; $Id: slicer3.pro,v 1.41 2004/01/21 15:55:02 scottm Exp $
;
; Copyright (c) 1993-2004, Research Systems, Inc.  All rights reserved.
;	Unauthorized reproduction prohibited.
;
;+
; NAME:
;       SLICER3
;
; PURPOSE:
;       Widget based application to visualize 3D data.
;       This program superseeds the "SLICER" program.
;
; CATEGORY:
;       Volume display / rendering.
;
; CALLING SEQUENCE:
;
;       SLICER3 [, hData3D]
;
; INPUTS:
;       hData3D:    A pointer to the 3D data, or an array of pointers
;                   to multiple 3D arrays.   If multiple arrays are specified,
;                   they all must have the same X, Y, and Z dimensions.
;                   This parameter is optional.   The default is to use a 3D
;                   array created from BYTARR(2,2,2).   While running SLICER3,
;                   the user may interactively load data via the file menu
;                   (see example).   If data is loaded in this fashion,
;                   any data passed to SLICER3 via a pointer (or pointers) is
;                   deleted, and the pointers become invalid.
;
; KEYWORD PARAMETERS:
;       DETACH:     If set, then the drawing area is placed in a base that is
;                   detached from the control panel.   The drawing area can
;                   only be detached if Slicer3 is not run in modal mode.
;       MODAL:      If set, then Slicer3 will block user interaction with all
;                   other widgets (and block the command line) until the user
;                   quits Slicer3.   If Slicer3 is started from some other
;                   widget-based application, then it is usually advisable
;                   to run Slicer3 in Modal mode.
;       GROUP:      This keyword specifies a widget ID of the group leader.
;                   If the specified widget is destroyed, Slicer3 is also
;                   destroyed.   If Slicer3 is started from a widget
;                   application, then GROUP should ALWAYS be specified.
;                   See example.
;       DATA_NAMES: A string array of names for the data. The names appear
;                   on the droplist widget for the current data. If the
;                   number of elements of DATA_NAMES is less than the
;                   number of elements in hData3D then default names will
;                   be generated for the unnamed data.
;
; COMMON BLOCKS:
;       COMMON colors, r, g, b, cur_red, cur_green, cur_blue
;                   These common variables are used by the "STRETCH",
;                   "LOADCT", and "XLOADCT" commands.
;
; SIDE EFFECTS:
;                   Slicer3 modifies the current color table, the device's
;                   decomposed setting (on 24-bit displays), as well as
;                   various elements of the plotting system (ie, the "!X",
;                   "!Y", "!Z", and "!P" system variables).
;                   If the "MODAL" keyword is set (usually a good idea),
;                   then SLICER3 will, upon exit, restore these system
;                   variables, settings, and the color tables to the values
;                   they had when SLICER3 was started.
;                   Slicer3 sets the position for the light source and
;                   enables back-facing polygons to be drawn (see the
;                   IDL "SET_SHADING" command).
;
;                   Slicer3 overwrites the existing contents of the
;                   Z-buffer.   Upon exiting Slicer3, the Z-buffer contents
;                   are the same as what was last displayed by Slicer3.
;
;                   Slicer3 breaks the color table into 6 "bands", based upon
;                   the number of available colors (max_color=!D.N_COLORS
;                   on 8-bit displays, and max_color=256 on 24-bit displays) :
;
;                      Band start index:    Band end index:    Used for:
;                      -----------------    ---------------    ---------
;
;                      0                    nColor-1           X Slices.
;                      nColor               (2*nColor)-1       Y Slices.
;                      2*nColor             (3*nColor)-1       Z Slices.
;                      3*nColor             (4*nColor)-1       Iso-surfaces.
;                      4*nColor             (5*nColor)-1       Projections.
;
;                      Where:
;                              nColor = (max_color - 9) / 5
;
;                      Note that the value of !D.N_Colors can vary from
;                      machine to machine, and from run to run, depending
;                      upon available system resources.   Also, !D.N_Colors
;                      is usually not set by IDL until the first window has
;                      been created (or realized) in that IDL session.
;
;                   Annotation colors are the last "band", and they are
;                   set up as :
;
;                      Color index:     Color:
;                      -------------    ------
;
;                      max_color - 1    White.
;                      max_color - 2    Yellow.
;                      max_color - 3    Cyan.
;                      max_color - 4    Purple.
;                      max_color - 5    Red.
;                      max_color - 6    Green.
;                      max_color - 7    Blue.
;                      max_color - 8    Black.
;
;                   On 24-bit displays, improved performance can often be
;                   gained by running Slicer3 in 8-bit mode.   This can be
;                   accomplished (on some platforms) by entering the following
;                   command at the start of the IDL session (before any
;                   windows are created):
;
;                      Device, Pseudo_Color=8
;
;                   See the documentation for additional information.
;
; RESTRICTIONS:
;       The data used by Slicer3 must meet the following conditions:
;          * The data must have three dimensions.
;          * The minimum size of the data array must be 2x2x2.
;          * If multiple volumes are loaded, they all must have the
;            same dimensions.
;
; PROCEDURE:
;
;       "File" menu:
;
;          "Load":
;                  Select a file containing a 3D array (or arrays) to load
;                  into Slicer3.   The file must have been written in a
;                  certain binary format.   For each data array in the file,
;                  the following values are present:
;
;                     data item                     data type     bytes
;                     --------------------------    ----------    ------
;
;                     Number of dimensions          long          4
;                     in array.   Note that
;                     this is always 3 for
;                     valid Slicer3 data.
;
;                     Size of first dimension.      long          4
;                     Size of second dimension.     long          4
;                     Size of third dimension.      long          4
;
;                        If multiple arrays are present in the file,
;                        they must all have the same dimensions.
;
;                     Data type (1 through 5)       long          4
;                     (see the IDL "SIZE"
;                     function for types).
;
;                     Total number of elements.     long          4
;                     (dimX*dimY*dimZ).
;
;                        Note that the all of the above values are the
;                        exact output of the IDL "SIZE" function.
;
;                     Number of characters          long          4
;                     in data name.
;
;                        Note that the above value is the output from
;                        the IDL "STRLEN" function.
;
;                     Data name.                    byte          strlen()
;
;                     3D data.                      varies        varies
;
;                     Note that the 3D data type and number of bytes
;                     is specified by the "size" information above.
;
;                  Any number of 3D datasets can be concatenated into
;                  a single file of this type (as long as they all have
;                  the same dimensions).
;
;                  (See EXAMPLE, below.)
;
;                  NOTE: Files saved by the "Save Subset" operation
;                  (see below) are suitable for input via the "Load"
;                  operation.
;
;                  Data files that are moved from one platform to
;                  another may not load as expected, due to differing
;                  byte order.   See the "BYTEORDER" and "SWAP_ENDIAN"
;                  IDL commands for details.
;
;          "Save / Save Subset":
;                  Slicer3 must be in "Block" mode for this operation to be
;                  available.   When selected, a subset of the 3D data
;                  enclosed in the current block is written to the chosen
;                  save file.   This subset can then be loaded back into
;                  Slicer3 at any time.   If multiple 3D arrays are
;                  currently available in Slicer3, then multiple subsets
;                  are saved to the file.
;
;          "Save / Save Tiff Image":
;                  When selected, a tiff image of the current Slicer3
;                  contents is saved to the chosen file.   When running in
;                  8-bit mode, a "Class P" palette color Tiff file is created.
;                  In 24-bit mode, a "Class R" (interleaved by image) Tiff
;                  file is created.
;
;          "Quit":
;                  Exits Slicer3.
;
;
;       "Tools" menu:
;
;          "Erase":
;                  Erases the display window and deletes all the objects
;                  in the display list.
;
;          "Delete / ...":
;                  As graphical objects are created, they are added to the
;                  display list.   The "Delete" menu allows the user to
;                  delete a specific object from the list.   When an object
;                  is deleted, the screen is redrawn with the remaining
;                  objects.
;
;          "Colors / Reset Colors":
;                  Selecting this will cause the original color scheme to
;                  be restored.
;
;          "Colors / Differential Shading":
;                  This allows the user to change the percentage of
;                  differential shading applied to the X, Y, and Z slices.
;
;          "Colors / Slice/Block":
;                  This allows the user to use the "XLOADCT" operation
;                  to modify the colors used for slices and blocks.
;                  In some cases, the new colors will not be visible
;                  until the user selects "Done" in the XLOADCT tool.
;
;          "Colors / Surface":
;                  This allows the user to use the "XLOADCT" operation
;                  to modify the colors used for iso-surfaces.
;
;          "Colors / Projection":
;                  This allows the user to use the "XLOADCT" operation
;                  to modify the colors used for projections.
;
;          Note that on some platforms, the selected colors may not
;          become visible until after the "XLOADCT" tool is exited.
;
;          "Options":
;                  This brings up a panel allowing the user to set:
;                     The axis visibility.
;                     The wire-frame cube visibility.
;                     The display window size
;                        (the X and Y dimensions are always the same).
;                  If the user selects "Ok", then the display is redrawn.
;
;
;       "About" menu:
;
;          "About Slicer":
;                  Brings up help information about Slicer3.
;
;
;       "Data:" pull-down menu:
;                  If multiple datasets are currently available in Slicer3,
;                  this menu allows the selection of the current data.
;                  Slices, blocks, iso-surfaces, etc. are created from
;                  the currently selected data.   If only one dataset
;                  is currently loaded, then this menu is inactive.
;
;
;       "Mode:" pull-down menu:
;                  This menu is used to select the current mode of operation.
;
;
;       Main Draw Window:
;                  Interaction in the main draw window is dependent upon
;                  the currently selected mode ("Slice", "Block", "Surface",
;                  etc., see below).   In general, when coordinate input is
;                  required from the user, it is performed by clicking a
;                  mouse button on the "surface" of the wire-frame cube that
;                  surrounds the data.   This 3D location is then used as
;                  the basis for whatever input is needed.   In most cases,
;                  the "front" side of the cube is used.   In a few cases,
;                  the coordinate input is on the "back" side of the cube.
;
;
;       "Slice" mode:
;                  To display a slice, click and drag the left mouse button
;                  on the wire-frame cube.   When the button is released, a
;                  slice through the data will be drawn at that location.
;
;          "Draw" mode:
;                  When in Draw mode, new slices will be merged into
;                  the current Z-buffer contents.
;
;          "Expose" mode:
;                  When in Expose mode, new slices will be drawn in
;                  front of everything else.
;
;          "Orthogonal" mode:
;                  When in Orthogonal mode, use the left mouse button
;                  in the big window to position and draw an orthogonal
;                  slicing plane.   Clicking the right mouse button in
;                  the big window (or any mouse button in the small
;                  window) will toggle the slicing plane orientation.
;             "X":
;                  This sets the orthogonal slicing plane orientation
;                  to be perpendicular to the X axis.
;             "Y":
;                  This sets the orthogonal slicing plane orientation
;                  to be perpendicular to the Y axis.
;             "Z":
;                  This sets the orthogonal slicing plane orientation
;                  to be perpendicular to the Z axis.
;
;          "Oblique" mode:
;                  Clicking any mouse button in the small window will
;                  reset the oblique slicing plane to its default
;                  orientation.
;             "Normal" mode:
;                  When in this mode, click and drag the left mouse
;                  button in the big window to set the surface normal
;                  for the oblique slicing plane.
;             "Center" mode:
;                  When in this mode, click and drag the left mouse
;                  button in the big window to set the center point
;                  for the surface normal.
;             "Display":
;                  Clicking this button will cause an oblique slicing
;                  plane to be drawn.
;
;
;       "Block" mode:
;                  When in Block mode, use the left mouse button in the
;                  big window to set the location for the "purple" corner
;                  of the block.   Use the right mouse button to locate
;                  the opposite "blue" corner of the block.
;
;                  When in Block mode, the "Save Subset" operation under
;                  the main "File" menu is available.
;
;             "Add" mode:
;                  When in this mode, the block will be "added" to the
;                  current Z-buffer contents.
;
;             "Subtract" mode:
;                  When in this mode, the block will be "subtracted"
;                  from the current Z-buffer contents.   Subtract mode
;                  is only effective when the block intersects some
;                  other object in the display (such as an iso-surface).
;
;             "Display":
;                  Clicking this button will cause the block to be drawn.
;
;
;        "Surface" mode:
;                  In iso-surface is like a contour line on a contour
;                  map.   On one side of the line, the elevation is higher
;                  than the contour level, and on the other side of the
;                  line, the elevation is lower than the contour level.
;                  An iso-surface, however, is a 3D surface that passes
;                  through the data such that the data values on one side
;                  of the surface are higher than the threshold value,
;                  and on the other side of the surface, the data values
;                  are lower than the threshold value.
;
;                  When in Surface mode, a logarithmic histogram plot
;                  of the data is displayed in the small draw window.
;                  Click and drag a mouse button on this plot to set
;                  the iso-surface threshold value.   This value is
;                  also shown in the text widget below the plot.
;                  The threshold value may also be set by typing a
;                  new value in this text widget.   The histogram
;                  plot is affected by the current threshold settings.
;                  (See Threshold mode, below).
;
;             "Low":
;                  Selecting this mode will cause the iso-surface polygon
;                  facing to face towards the lower data values.
;                  Usually, this is the mode to use when the iso-surface
;                  is desired to surround high data values.
;
;             "High":
;                  Selecting this mode will cause the iso-surface polygon
;                  facing to face towards the higher data values.
;                  Usually, this is the mode to use when the iso-surface
;                  is desired to surround low data values.
;
;             "Shading" pull-down menu:
;                  Iso-surfaces are normally rendered with light-source
;                  shading.   If multiple datasets are currently loaded,
;                  then this menu allows the selection of a different
;                  3D array for the source of the iso-surface shading
;                  values.   If only one dataset is currently loaded,
;                  then this menu is inactive.
;
;             "Display":
;                  Clicking this button will cause the iso-surface to
;                  be created and drawn.   Iso-surfaces often consist
;                  of tens of thousands of polygons, and can sometimes
;                  take considerable time to create and render.
;
;
;        "Projection" mode:
;                  A "voxel" projection of a 3D array is the projection
;                  of the data values within that array onto a viewing
;                  plane.   This is similar to taking an X-ray image of
;                  a 3D object.
;
;             "Max" mode:
;                  Select this mode for a Maximum intensity projection.
;
;             "Avg" mode:
;                  Select this mode for an Average intensity projection.
;
;             "Low" mode:
;                  Select this mode for a Low resolution projection.
;
;             "Med" mode:
;                  Select this mode for a Medium resolution projection.
;
;             "High" mode:
;                  Select this mode for a High resolution projection.
;
;             "Depth Queue %":
;                  Use the slider to set the depth queue percent.
;                  A value of 50, for example, indicates that the
;                  farthest part of the projection will be 50 % as
;                  bright as the closest part of the projection.
;
;             "Display":
;                  Clicking this button will cause the projection to
;                  be calculated and drawn.   Projections can sometimes
;                  take considerable time to display.   Higher resolution
;                  projections take more computation time.
;
;
;        "Threshold" mode:
;                  When in Threshold mode, a logarithmic histogram plot
;                  of the data is displayed in the small draw window.
;                  Click and drag the left mouse button on this plot to
;                  set the minimum and maximum threshold values.
;                  To expand a narrow range of data values into the
;                  full range of available colors, set the threshold
;                  range before displaying slices, blocks, or projections.
;                  The threshold settings also affect the histogram
;                  plot in "Surface" mode.   The minimum and maximum
;                  threshold values are also shown in the text widgets
;                  below the histogram plot.
;
;                  Click and drag the right mouse button on the histogram
;                  plot to set the transparency threshold.
;                  Portions of any slice, block, or projection that are
;                  less than the transparency value are not drawn (clear).
;                  Iso-surfaces are not affected by the transparency
;                  threshold.   The transparency threshold value is also
;                  shown in a text widget below the histogram plot.
;
;           "Min":
;                  In this text widget, a minimum threshold value can
;                  be entered.
;
;           "Max":
;                  In this text widget, a maximum threshold value can
;                  be entered.
;
;           "Transp.":
;                  In this text widget, a transparency threshold value
;                  can be entered.
;
;
;        "Profile" mode:
;                  In Profile mode, a plot is displayed showing the
;                  data values along a line.   This line is also shown
;                  superimposed on the data in the main draw window.
;                  The bottom of the plot corresponds to the "purple"
;                  end of the line, and the top of the plot corresponds
;                  to the "blue" end of the line.
;
;           "Orthogonal" mode:
;                  Click and drag the left mouse button to position the
;                  profile line, based upon a point on the "front"
;                  faces of the wire-frame cube.   Click and drag the
;                  right mouse button to position the profile line,
;                  based upon a point on the "back" faces of the
;                  wire-frame cube.   As the profile line is moved,
;                  The profile plot is dynamically updated.
;
;           "Oblique" mode:
;                  Click and drag the left mouse button to position the
;                  "purple" end of the profile line on one of the "front"
;                  faces of the wire-frame cube.   Click and drag the
;                  right mouse button to position the "blue" end of the
;                  profile line on one of the "back" faces of the
;                  wire-frame cube.   As the profile line is moved,
;                  The profile plot is dynamically updated.
;
;
;        "Probe" mode:
;                  In Probe mode, click and drag a mouse button over
;                  an object in the main draw window.   The actual
;                  X-Y-Z location within the data volume is displayed
;                  in the three text widgets.   Also, the data value
;                  at that 3D location is displayed in the status
;                  window, above the main draw window.   If the cursor
;                  is inside the wire-frame cube, but not on any object,
;                  then the status window displays "No data value", and
;                  the three text widgets are empty.  If the cursor is
;                  outside the wire-frame cube, then the status window
;                  and text widgets are empty.
;
;           "X":
;                  Use this text widget to enter the X coordinate for
;                  the probe.
;
;           "Y":
;                  Use this text widget to enter the Y coordinate for
;                  the probe.
;
;           "Z":
;                  Use this text widget to enter the Z coordinate for
;                  the probe.
;
;
;        "View" mode:
;                  In view mode, a small window shows the orientation
;                  of the data cube in the current view.   As view
;                  parameters are changed, this window is dynamically
;                  updated.   The main draw window is then updated
;                  when the user clicks on "Display", or exits View
;                  mode.
;
;        "Display":
;                  Clicking on this button will cause the objects in
;                  the main view window to be drawn in the new view.
;                  If any view parameters have been changed since
;                  the last time the main view was updated, the main
;                  view will be automatically redrawn when the user
;                  exits View mode.
;
;        1st Rotation:
;                  Use this slider to set the angle of the first view
;                  rotation (in degrees).   The droplist widget adjacent
;                  to the slider indicates which axis this rotation is
;                  about.
;
;        2nd Rotation:
;                  Use this slider to set the angle of the second view
;                  rotation (in degrees).   The droplist widget adjacent
;                  to the slider indicates which axis this rotation is
;                  about.
;
;        "Zoom %":
;                  Use this slider to set the zoom factor percent.
;                  Depending upon the view rotations, Slicer3 may
;                  override this setting to ensure that all eight
;                  corners of the data cube are within the window.
;
;        "Z %":
;                  Use this slider to set a scale factor for the Z
;                  axis (to compensate for the data's aspect ratio).
;
;
; EXAMPLE:
;       Example 1:
;       ----------
;       Create a data save file suitable for dynamic loading into
;       Slicer3.
;
;
;               ; Store some 3D data in a variable called "data_1".
;               data_1 = INDGEN(20,30,40)
;
;               ; Store some 3D data in a variable called "data_2".
;               data_2 = FINDGEN(20,30,40)
;
;               ; Define the names for the datasets (their names will
;               ; appear in the "Data:" pull-down menu in Slicer3.
;
;               data_1_name = 'Test Data 1'
;               data_2_name = 'Data 2'
;
;               ; Select a data file name.
;               dataFile = DIALOG_PICKFILE()
;
;               ; Write the file.
;
;               GET_LUN, lun
;               OPENW, lun, dataFile
;
;               WRITEU, lun, SIZE(data_1)
;               WRITEU, lun, STRLEN(data_1_name)
;               WRITEU, lun, BYTE(data_1_name)
;               WRITEU, lun, data_1
;
;               WRITEU, lun, SIZE(data_2)
;               WRITEU, lun, STRLEN(data_2_name)
;               WRITEU, lun, BYTE(data_2_name)
;               WRITEU, lun, data_2
;
;               CLOSE, lun
;               FREE_LUN, lun
;
;
;       Example 2:
;       ----------
;       Run Slicer3 with data passed to it at startup.
;
;               ; Create some 3D data.
;               data = INDGEN(20,30,40)
;
;               ; Create a pointer to the data, and use the "/NO_COPY"
;               ; keyword to save memory.
;               h_data = PTR_NEW(data, /NO_COPY)
;
;               ; Start up Slicer3.
;               SLICER3, h_data, /MODAL
;
;               ; If the user did not interactively load any data into
;               ; Slicer3 (via the "File/Load" menu), then the original
;               ; pointer to the data still exists (and the original data
;               ; will still reside in memory).   To free it, use:
;
;               if PTR_VALID(h_data) then PTR_FREE, h_data
;
;               ; If the pointer is no longer valid, then that indicates
;               ; that the user interactively loaded data into Slicer3.
;               ; Any data that is loaded interactively is automatically
;               ; deleted when the user exits Slicer3.
;
;               ; Note that the last contents of the main view window in
;               ; Slicer3 still resides in the Z-buffer.   To access this
;               ; image after exiting Slicer3, perform the following actions:
;
;               current_device = !D.Name
;               SET_PLOT, 'Z'
;               image_buffer = TVRD()
;               depth_buffer = TVRD(CHANNEL=1, /WORDS)
;               SET_PLOT, current_device
;               TV, image_buffer
;
;               ; Note that the image contained in "image_buffer" will look
;               ; "correct" only if the colors loaded by Slicer3 have not
;               ; been changed since the user exited Slicer3.
;
;
; MODIFICATION HISTORY:
;       Daniel Carr - RSI, Fri Nov 22 15:43:36 MST 1996
;       Daniel Carr - RSI, Fri Jan 10 12:08:01 MST 1997
;          Fixed bugs and added muti-dataset capability.
;       Alan Youngblood, Daniel Carr - RSI, Wed Feb 11 10:07:32 MST 1998
;          Modified routine to use pointers.
;       CT, RSI, June 2000: Remove /MODAL from XMANAGER, add to WIDGET_BASE.
;          If modal then use button menus rather than menu bar.
;          Change TIFF output to use color-interleaving (channel,column,row).
;       CT, RSI, Sept 2002: Add ORIENTATION=4 to WRITE_TIFF calls.
;
;
;-

;******************************************************************************
; Procedure to reset IDL system variables to their initial start-up states.
pro Viz3D_Reset

   COMPILE_OPT hidden, idl2

   !Order = 0

   T3d, /Reset
   !P.T3d = 0
   !P.Position = [0.0, 0.0, 0.0, 0.0]
   !P.Clip = [0L, 0L, (!D.X_Size-1L), (!D.Y_Size-1L), 0L, 0L]
   !P.Region = [0.0, 0.0, 0.0, 0.0]
   !P.Background = 0L
   !P.Charsize = 0.0
   !P.Charthick = 0.0
   !P.Color = 255
   !P.Font = (-1L)
   !P.Linestyle = 0L
   !P.Multi = [0L, 0L, 0L, 0L, 0L]
   !P.Noclip = 0L
   !P.Noerase = 0L
   !P.Nsum = 0L
   !P.Psym = 0L
   !P.Subtitle = ''
   !P.Symsize = 0.0
   !P.Thick = 0.0
   !P.Title = ''
   !P.Ticklen = 0.02
   !P.Channel = 0

   !X.S = [0.0, 0.0]
   !X.Style = 0L
   !X.Range = [0.0, 0.0]
   !X.Margin = [10.0, 3.0]
   !X.Type = 0L
   !X.Ticks = 0L
   !X.Ticklen = 0.0
   !X.Thick = 0.0
   !X.Crange = [0.0, 0.0]
   !X.Omargin = [0.0, 0.0]
   !X.Window = [0.0, 0.0]
   !X.Region = [0.0, 0.0]
   !X.Charsize = 0.0
   !X.Minor = 0L
   !X.Tickv = Replicate(0.0, 30)
   !X.Tickname = Replicate('', 30)
   !X.Gridstyle = 0L
   !X.Tickformat = ''
   !X.Title = ''

   !Y.S = [0.0, 0.0]
   !Y.Style = 0L
   !Y.Range = [0.0, 0.0]
   !Y.Margin = [4.0, 2.0]
   !Y.Type = 0L
   !Y.Ticks = 0L
   !Y.Ticklen = 0.0
   !Y.Thick = 0.0
   !Y.Crange = [0.0, 0.0]
   !Y.Omargin = [0.0, 0.0]
   !Y.Window = [0.0, 0.0]
   !Y.Region = [0.0, 0.0]
   !Y.Charsize = 0.0
   !Y.Minor = 0L
   !Y.Tickv = Replicate(0.0, 30)
   !Y.Tickname = Replicate('', 30)
   !Y.Gridstyle = 0L
   !Y.Tickformat = ''
   !Y.Title = ''

   !Z.S = [0.0, 0.0]
   !Z.Style = 0L
   !Z.Range = [0.0, 0.0]
   !Z.Margin = [0.0, 0.0]
   !Z.Type = 0L
   !Z.Ticks = 0L
   !Z.Ticklen = 0.0
   !Z.Thick = 0.0
   !Z.Crange = [0.0, 0.0]
   !Z.Omargin = [0.0, 0.0]
   !Z.Window = [0.0, 0.0]
   !Z.Region = [0.0, 0.0]
   !Z.Charsize = 0.0
   !Z.Minor = 0L
   !Z.Tickv = Replicate(0.0, 30)
   !Z.Tickname = Replicate('', 30)
   !Z.Gridstyle = 0L
   !Z.Tickformat = ''
   !Z.Title = ''

end
;******************************************************************************


;******************************************************************************
; Procedure to set the differential shading.
pro Viz3D_DiffColor, sViz3DColors
   COMPILE_OPT hidden, idl2

   ; IDL colors common block (used by "LOADCT" and "STRETCH").
   common colors, r, g, b, cur_red, cur_green, cur_blue

   fdiffShade = Float(sViz3DColors.diffShade) / 100.0

   cR1 = (sViz3DColors.rSlice * (1.0 - fdiffshade))
   cG1 = (sViz3DColors.gSlice * (1.0 - fdiffshade))
   cB1 = (sViz3DColors.bSlice * (1.0 - fdiffshade))
   TVLCT, cR1, cG1, cB1, 0

   cR1 = (sViz3DColors.rSlice * (1.0 - fdiffshade)) + $
         (127.0 * fdiffshade)
   cG1 = (sViz3DColors.gSlice * (1.0 - fdiffshade)) + $
         (127.0 * fdiffshade)
   cB1 = (sViz3DColors.bSlice * (1.0 - fdiffshade)) + $
         (127.0 * fdiffshade)
   TVLCT, cR1, cG1, cB1, sViz3DColors.nColor

   cR1 = (sViz3DColors.rSlice * (1.0 - fdiffshade)) + $
         (255.0 * fdiffshade)
   cG1 = (sViz3DColors.gSlice * (1.0 - fdiffshade)) + $
         (255.0 * fdiffshade)
   cB1 = (sViz3DColors.bSlice * (1.0 - fdiffshade)) + $
         (255.0 * fdiffshade)
   TVLCT, cR1, cG1, cB1, (2 * sViz3DColors.nColor)

   TVLCT, cur_red, cur_green, cur_blue, /GET
   sViz3DColors.cR = cur_red
   sViz3DColors.cG = cur_green
   sViz3DColors.cB = cur_blue

end
;******************************************************************************


;******************************************************************************
; Function to load a color table and set the annotation colors.
; Returns the color state.
function Viz3D_LoadColor, CTAB=cTab, DIFFSHADE=diffShade
   COMPILE_OPT hidden, idl2

   ; IDL colors common block (used by "LOADCT" and "STRETCH").
   common colors, r, g, b, cur_red, cur_green, cur_blue

   ;Annotation color numbers :
   ; 0 - Black.
   ; 1 - Blue.
   ; 2 - Green.
   ; 3 - Red.
   ; 4 - Purple.
   ; 5 - Cyan.
   ; 6 - Yellow.
   ; 7 - White.

   nColor = ((!D.N_Colors < 256) - 9) / 5
   white  = LONG([255, 255, 255])
   yellow = LONG([255, 255,   0])
   cyan   = LONG([  0, 255, 255])
   purple = LONG([191,   0, 191])
   red    = LONG([255,   0,   0])
   green  = LONG([  0, 255,   0])
   blue   = LONG([ 63,  63, 255])
   black  = LONG([  0,   0,   0])

   if (N_ELEMENTS(cTab) le 3L) then cTab = [3,1,8]

   LOADCT, cTab[0]
   STRETCH, 0, (nColor-1)
   rSlice = cur_red[0:nColor-1]
   gSlice = cur_green[0:nColor-1]
   bSlice = cur_blue[0:nColor-1]

   LOADCT, cTab[1]
   STRETCH, 0, (nColor-1)
   rSurfc = cur_red[0:nColor-1]
   gSurfc = cur_green[0:nColor-1]
   bSurfc = cur_blue[0:nColor-1]

   LOADCT, cTab[2]
   STRETCH, 0, (nColor-1)
   rPrjct = cur_red[0:nColor-1]
   gPrjct = cur_green[0:nColor-1]
   bPrjct = cur_blue[0:nColor-1]

   DEVICE, GET_VISUAL_DEPTH = visDepth
   if (visDepth gt 8) then displayBits = 24 else displayBits = 8

   nCol = !D.N_Colors < 256

   cWhite = nCol - 1
   cYellow = nCol - 2
   cCyan = nCol - 3
   cPurple = nCol - 4
   cRed = nCol - 5
   cGreen = nCol - 6
   cBlue = nCol - 7
   cBlack = nCol - 8

   colors8 = [cBlack,cBlue,cGreen,cRed,cPurple,cCyan,cYellow,cWhite]

   TVLCT, rSurfc, gSurfc, bSurfc, (3 * nColor)
   TVLCT, rPrjct, gPrjct, bPrjct, (4 * nColor)
   TVLCT, REFORM(white, 1, 3), cWhite
   TVLCT, REFORM(yellow, 1, 3), cYellow
   TVLCT, REFORM(cyan, 1, 3), cCyan
   TVLCT, REFORM(purple, 1, 3), cPurple
   TVLCT, REFORM(red, 1, 3), cRed
   TVLCT, REFORM(green, 1, 3), cGreen
   TVLCT, REFORM(blue, 1, 3), cBlue
   TVLCT, REFORM(black, 1, 3), cBlack

   sViz3DColors = {SViz3DColors, nColor:nColor, diffShade:diffShade, $
                   colors8:colors8, $
                   displayBits:displayBits, $
                   cR:cur_red, cG:cur_green, cB:cur_blue, $
                   rSlice:rSlice, gSlice:gSlice, bSlice:bSlice}

   Viz3D_DiffColor, sViz3DColors

   TVLCT, cur_red, cur_green, cur_blue, /Get

   return, sViz3DColors

end
;******************************************************************************


;******************************************************************************
; Function to translate an annotation color
; number to an actual color table index.
function Viz3D_TransColor, sMainState, cIndex
      COMPILE_OPT hidden, idl2

      return, sMainState.sColorState.colors8[cIndex]

end
;******************************************************************************


;******************************************************************************
; Function to set up the 3D view.
; Returns the view state.
function Viz3D_View, viewWin, $
                     XMAX=xMax, YMAX=yMax, ZMAX=zMax, $
                     ANG1=ang1, ANG2=ang2, ANG3=ang3, $
                     DIR1=dir1, DIR2=dir2, DIR3=dir3, $
                     ZOOM=zoomFac, ZSCALE=zScale, PERSP=pDist

   COMPILE_OPT hidden, idl2

   if (N_ELEMENTS(xMax) le 0L) then xMax = 1.0
   if (N_ELEMENTS(yMax) le 0L) then yMax = 1.0
   if (N_ELEMENTS(zMax) le 0L) then zMax = 1.0

   if (N_ELEMENTS(ang1) le 0L) then ang1 = 0.0
   if (N_ELEMENTS(ang2) le 0L) then ang2 = 0.0
   if (N_ELEMENTS(ang3) le 0L) then ang3 = 0.0
   if (N_ELEMENTS(dir1) le 0L) then dir1 = 'Z'
   if (N_ELEMENTS(dir2) le 0L) then dir2 = 'Y'
   if (N_ELEMENTS(dir3) le 0L) then dir3 = 'X'
   if (N_ELEMENTS(zoomFac) le 0L) then zoomFac = 1.0
   if (N_ELEMENTS(zScale) le 0L) then zScale = 1.0
   if (N_ELEMENTS(pDist) le 0L) then pDist = 0.0

   s_ang1 = Sin(ang1*!Dtor)
   c_ang1 = Cos(ang1*!Dtor)
   s_ang2 = Sin(ang2*!Dtor)
   c_ang2 = Cos(ang2*!Dtor)
   s_ang3 = Sin(ang3*!Dtor)
   c_ang3 = Cos(ang3*!Dtor)

   ident4 = FLTARR(4, 4)
   ident4[[0,5,10,15]] = 1.0

   vTrans = ident4

   ; First translation.
   m4X4 = ident4
   m4X4[[3,7,11]] = (-0.5)
   vTrans = TEMPORARY(vTrans) # m4X4

   ; Scale for aspect ratio.
   xRange = FLOAT(xMax)
   yRange = FLOAT(yMax)
   zRange = FLOAT(zMax) * (zScale > 1.0)
   maxRange = xRange > yRange > zRange
   xyzFac = [xRange, yRange, zRange] / maxRange
   m4X4 = ident4
   m4X4[0,0] = xyzFac[0]
   m4X4[1,1] = xyzFac[1]
   m4X4[2,2] = xyzFac[2]
   vTrans = TEMPORARY(vTrans) # m4X4

   ; Z scale.
   if (zScale lt 1.0) then begin
      m4X4 = ident4
      m4x4[2,2] = zScale
      vTrans = TEMPORARY(vTrans) # m4X4
   endif
   if (zScale gt 1.0) then begin
      zFac = 1.0 / zScale
      m4X4 = ident4
      m4x4[0,0] = zFac
      m4x4[1,1] = zFac
      vTrans = TEMPORARY(vTrans) # m4X4
   endif

   ; First rotation.
   m4x4 = ident4
   case dir1 of
      'X': begin ; Rotate about x.
         m4x4[1,1] = c_ang1
         m4x4[1,2] = s_ang1
         m4x4[2,1] = (-s_ang1)
         m4x4[2,2] = c_ang1
      end
      'Y': begin ; Rotate about y.
         m4x4[0,0] = c_ang1
         m4x4[0,2] = (-s_ang1)
         m4x4[2,0] = s_ang1
         m4x4[2,2] = c_ang1
      end
      'Z': begin ; Rotate about z.
         m4x4[0,0] = c_ang1
         m4x4[0,1] = s_ang1
         m4x4[1,0] = (-s_ang1)
         m4x4[1,1] = c_ang1
      end
   endcase
   vTrans = TEMPORARY(vTrans) # m4X4

   ; Second rotation.
   m4x4 = ident4
   case dir2 of
      'X': begin ; Rotate about x.
         m4x4[1,1] = c_ang2
         m4x4[1,2] = s_ang2
         m4x4[2,1] = (-s_ang2)
         m4x4[2,2] = c_ang2
      end
      'Y': begin ; Rotate about y.
         m4x4[0,0] = c_ang2
         m4x4[0,2] = (-s_ang2)
         m4x4[2,0] = s_ang2
         m4x4[2,2] = c_ang2
      end
      'Z': begin ; Rotate about z.
         m4x4[0,0] = c_ang2
         m4x4[0,1] = s_ang2
         m4x4[1,0] = (-s_ang2)
         m4x4[1,1] = c_ang2
      end
   endcase
   vTrans = TEMPORARY(vTrans) # m4X4

   ; Third rotation.
   m4x4 = ident4
   case dir3 of
      'X': begin ; Rotate about x.
         m4x4[1,1] = c_ang3
         m4x4[1,2] = s_ang3
         m4x4[2,1] = (-s_ang3)
         m4x4[2,2] = c_ang3
      end
      'Y': begin ; Rotate about y.
         m4x4[0,0] = c_ang3
         m4x4[0,2] = (-s_ang3)
         m4x4[2,0] = s_ang3
         m4x4[2,2] = c_ang3
      end
      'Z': begin ; Rotate about z.
         m4x4[0,0] = c_ang3
         m4x4[0,1] = s_ang3
         m4x4[1,0] = (-s_ang3)
         m4x4[1,1] = c_ang3
      end
   endcase
   vTrans = TEMPORARY(vTrans) # m4X4

   ; Perspective.
   if (pDist gt 0.0) then begin
      pD2 = SQRT(pDist)
      m4X4 = ident4
      m4x4[2,3] = (-1.0 / pD2)
      vTrans = TEMPORARY(vTrans) # m4X4
   endif

   ; Zoom.
   m4X4 = ident4
   m4X4[0,0] = zoomFac
   m4X4[1,1] = zoomFac
   m4X4[2,2] = zoomFac
   vTrans = TEMPORARY(vTrans) # m4X4

   ; Zoom down so that all 8 corners of the
   ; unit cube are within the view.
   corners = Fltarr(3,8)
   corners[*,0] = [0,0,0]
   corners[*,1] = [1,0,0]
   corners[*,2] = [1,1,0]
   corners[*,3] = [0,1,0]
   corners[*,4] = [0,0,1]
   corners[*,5] = [1,0,1]
   corners[*,6] = [1,1,1]
   corners[*,7] = [0,1,1]
   corners = VERT_T3D(corners, Matrix=vTrans, /NO_COPY)
   sFac = (1.0 / (1.0 + (2.0 * (MAX(ABS(corners[0:1,*])) - 0.5)))) < 1.0
   if (sFac lt 1.0) then begin
      m4X4 = ident4
      m4X4[0,0] = sFac
      m4X4[1,1] = sFac
      m4X4[2,2] = sFac
      vTrans = TEMPORARY(vTrans) # m4X4
   endif

   m4X4 = ident4
   m4x4[2,2] = (0.1)
   vTrans = TEMPORARY(vTrans) # m4X4

   ; Last translation.
   m4X4 = ident4
   m4X4[[3,7,11]] = (0.5)
   vTrans = TEMPORARY(vTrans) # m4X4

   ; System variables.
   !P.T = vTrans
   !X.S = [0.0, (1.0 / xRange)]
   !Y.S = [0.0, (1.0 / yRange)]
   !Z.S = [0.0, (1.0 / FLOAT(zMax))]

   sViz3DView = {SViz3DView, viewWin:viewWin, $
                 xMax:xMax, yMax:yMax, zMax:zMax, $
                 ang1:ang1, ang2:ang2, ang3:ang3, $
                 dir1:dir1, dir2:dir2, dir3:dir3, $
                 zoomFac:zoomFac, zScale:zScale, pDist:pDist, $
                 vTrans:vTrans, invTrans:INVERT(vTrans)}

   return, sViz3DView

end
;******************************************************************************


;******************************************************************************
; Procedure to save the depth buffer information for the cube's outer surface.
; This depth info is used to find the XYZ data coordinate on the surface of
; the cube, given a user selected screen XY coordinate.
pro Viz3D_FillDepth, sMainState

   COMPILE_OPT hidden, idl2

   SET_PLOT, 'Z'
   image = TVRD()
   depth = TVRD(CHANNEL=1, /WORDS)

   ; Front faces.
   ERASE, 0
   norm = [[0.0,0.0,0.0], [0.0,0.0,(-1.0)]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) gt 0.0) then $
      POLYFILL, [0,1,1,0], [0,0,1,1], [0,0,0,0], /NORMAL, /T3D, COLOR=1

   norm = [[0.0,0.0,0.0], [0.0,(-1.0),0.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) gt 0.0) then $
      POLYFILL, [0,1,1,0], [0,0,0,0], [0,0,1,1], /NORMAL, /T3D, COLOR=2

   norm = [[0.0,0.0,0.0], [(-1.0),0.0,0.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) gt 0.0) then $
      POLYFILL, [0,0,0,0], [0,1,1,0], [0,0,1,1], /NORMAL, /T3D, COLOR=3

   norm = [[1.0,1.0,1.0], [1.0,1.0,2.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) gt 0.0) then $
      POLYFILL, [0,1,1,0], [0,0,1,1], [1,1,1,1], /NORMAL, /T3D, COLOR=1

   norm = [[1.0,1.0,1.0], [1.0,2.0,1.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) gt 0.0) then $
      POLYFILL, [0,1,1,0], [1,1,1,1], [0,0,1,1], /NORMAL, /T3D, COLOR=2

   norm = [[1.0,1.0,1.0], [2.0,1.0,1.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) gt 0.0) then $
      POLYFILL, [1,1,1,1], [0,1,1,0], [0,0,1,1], /NORMAL, /T3D, COLOR=3

   *(sMainState.hFrontImage) = TVRD()
   *(sMainState.hFrontDepth) = TVRD(CHANNEL=1, /WORDS)

   ; Back faces.
   ERASE, 0

   norm = [[0.0,0.0,0.0], [0.0,0.0,(-1.0)]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) lt 0.0) then $
      POLYFILL, [0,1,1,0], [0,0,1,1], [0,0,0,0], /NORMAL, /T3D, COLOR=1

   norm = [[0.0,0.0,0.0], [0.0,(-1.0),0.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) lt 0.0) then $
      POLYFILL, [0,1,1,0], [0,0,0,0], [0,0,1,1], /NORMAL, /T3D, COLOR=2

   norm = [[0.0,0.0,0.0], [(-1.0),0.0,0.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) lt 0.0) then $
      POLYFILL, [0,0,0,0], [0,1,1,0], [0,0,1,1], /NORMAL, /T3D, COLOR=3

   norm = [[1.0,1.0,1.0], [1.0,1.0,2.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) lt 0.0) then $
      POLYFILL, [0,1,1,0], [0,0,1,1], [1,1,1,1], /NORMAL, /T3D, COLOR=1

   norm = [[1.0,1.0,1.0], [1.0,2.0,1.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) lt 0.0) then $
      POLYFILL, [0,1,1,0], [1,1,1,1], [0,0,1,1], /NORMAL, /T3D, COLOR=2

   norm = [[1.0,1.0,1.0], [2.0,1.0,1.0]]
   norm = VERT_T3D(norm, /NO_COPY)
   if ((norm[2,1]-norm[2,0]) lt 0.0) then $
      POLYFILL, [1,1,1,1], [0,1,1,0], [0,0,1,1], /NORMAL, /T3D, COLOR=3

   *(sMainState.hBackImage) = TVRD()
   *(sMainState.hBackDepth) = TVRD(CHANNEL=1, /WORDS)

   TV, TEMPORARY(image)
   TV, TEMPORARY(depth), CHANNEL=1, /WORDS
   SET_PLOT, sMainState.screenDevice

end
;******************************************************************************


;******************************************************************************
; Function to check out the current data.
function Viz3D_GetData, sMainState, curData

   COMPILE_OPT hidden, idl2

   if (N_ELEMENTS(curData) LE 0L) then curData = sMainState.curData

   hData3D = (*(sMainState.hDataList))[curData]

   return, TEMPORARY(*hData3D)

end
;******************************************************************************


;******************************************************************************
; Function to check in the current data.
pro Viz3D_PutData, data3D, sMainState, curData

   COMPILE_OPT hidden, idl2

   if (N_ELEMENTS(curData) LE 0L) then curData = sMainState.curData

   hData3D = (*(sMainState.hDataList))[curData]

   *hData3D = TEMPORARY(data3D)

end
;******************************************************************************


;******************************************************************************
; Function to scale the data into the specified thereshold range.
function Viz3D_ScaleData, data3D, sMainState

   COMPILE_OPT hidden, idl2

   lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
   hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
   return, BYTSCL(TEMPORARY(data3D), MIN=lTh, MAX=hTh, TOP=255B)

end
;******************************************************************************


;******************************************************************************
; Procedure to compute the intersection outline of the cube and the
; oblique slicing plane.   This procedure also draws the outline in
; the current window (either the big view window, or the small slice
; status window).
; The VERT_PLANE keyword returns the intersection coordinates in the
; image (oblique) plane.   The VERT_3D keyword returns the intersection
; coordinates as absolute 3D coordinates.   The plane and 3D coordinates
; will be necessary when drawing the data on the oblique plane.

pro Viz3D_DrawSliceOblique, obliqCenter, obliqNormal, fillColor, $
       edgeColor, rangeX, rangeY, rangeZ, zScale, NORMCOLOR=normColor, $
       SKIP_FILL=skipFill, VERT_PLANE=vertPlane, VERT_3D=vert3D

   COMPILE_OPT hidden, idl2

   ; Scale the oblique normal vector.
   zDim = rangeZ * zScale
   maxDim = rangeX > rangeY > zDim
   sliceNormU = obliqNormal
   sliceNormU[0] = obliqNormal[0] * rangeX / maxDim
   sliceNormU[1] = obliqNormal[1] * rangeY / maxDim
   sliceNormU[2] = obliqNormal[2] * zDim / maxDim

   ; Determine the rotation angles and build the transformation matrices.

   if ((sliceNormU[0] eq 0.0) and (sliceNormU[1] eq 0.0)) then angZ = 0.0 $
   else angZ = ATAN(sliceNormU[1], sliceNormU[0])

   pDistance = SQRT(sliceNormU[0]^2 + sliceNormU[1]^2)
   if ((pDistance eq 0.0) and (sliceNormU[2] eq 0.0)) then angY = 0.0 $
   else angY = ATAN(sliceNormU[2], pDistance)

   sZ = SIN(-angZ)
   cZ = COS(-angZ)
   sY = SIN(angY-(!PI/2.0))
   cY = COS(angY-(!PI/2.0))

   ident4 = FLTARR(4, 4)
   ident4[[0,5,10,15]] = 1.0

   vTrans1 = ident4
   ; First translation.
   m4X4 = ident4
   m4X4[[3,7,11]] = (-obliqCenter)
   vTrans1 = TEMPORARY(vTrans1) # m4X4
   ; Z rotation.
   m4X4 = ident4
   m4x4[0,0] = cZ
   m4x4[0,1] = sZ
   m4x4[1,0] = (-sZ)
   m4x4[1,1] = cZ
   vTrans1 = TEMPORARY(vTrans1) # m4X4
   ; Y rotation.
   m4X4 = ident4
   m4x4[0,0] = cY
   m4x4[0,2] = (-sY)
   m4x4[2,0] = sY
   m4x4[2,2] = cY
   vTrans1 = TEMPORARY(vTrans1) # m4X4

   sZ = SIN(angZ)
   cZ = COS(angZ)
   sY = SIN((!PI/2.0)-angY)
   cY = COS((!PI/2.0)-angY)

   ; Create vTrans2 as the inverse of vTrans1.
   vTrans2 = ident4
   ; Y rotation.
   m4X4 = ident4
   m4x4[0,0] = cY
   m4x4[0,2] = (-sY)
   m4x4[2,0] = sY
   m4x4[2,2] = cY
   vTrans2 = TEMPORARY(vTrans2) # m4X4
   ; Z rotation.
   m4X4 = ident4
   m4x4[0,0] = cZ
   m4x4[0,1] = sZ
   m4x4[1,0] = (-sZ)
   m4x4[1,1] = cZ
   vTrans2 = TEMPORARY(vTrans2) # m4X4
   ; First translation.
   m4X4 = ident4
   m4X4[[3,7,11]] = (+obliqCenter)
   vTrans2 = TEMPORARY(vTrans2) # m4X4

   verts = FLTARR(3, 8, /NOZERO)
   verts[0, 0] = [0,0,0]
   verts[0, 1] = [1,0,0]
   verts[0, 2] = [1,1,0]
   verts[0, 3] = [0,1,0]
   verts[0, 4] = [0,0,1]
   verts[0, 5] = [1,0,1]
   verts[0, 6] = [1,1,1]
   verts[0, 7] = [0,1,1]
   verts = VERT_T3D(verts, /NO_COPY, MATRIX=vTrans1)

   ; Search for intersection points.

   newXYZ = [0.0, 0.0, 0.0]

   ; Verts 0, 1.
   if ((verts[2,0]*verts[2,1]) le 0.0) then begin
      lenFac = verts[2,0] / (verts[2,0] - verts[2,1])
      newXYZ = [Temporary(newXYZ), verts[*,0] + $
         ((verts[*,1]-verts[*,0]) * lenFac)]
   endif
   ; Verts 0, 3.
   if ((verts[2,0]*verts[2,3]) le 0.0) then begin
      lenFac = verts[2,0] / (verts[2,0] - verts[2,3])
      newXYZ = [Temporary(newXYZ), verts[*,0] + $
         ((verts[*,3]-verts[*,0]) * lenFac)]
   endif
   ; Verts 0, 4.
   if ((verts[2,0]*verts[2,4]) le 0.0) then begin
      lenFac = verts[2,0] / (verts[2,0] - verts[2,4])
      newXYZ = [Temporary(newXYZ), verts[*,0] + $
         ((verts[*,4]-verts[*,0]) * lenFac)]
   endif
   ; Verts 1, 2.
   if ((verts[2,1]*verts[2,2]) le 0.0) then begin
      lenFac = verts[2,1] / (verts[2,1] - verts[2,2])
      newXYZ = [Temporary(newXYZ), verts[*,1] + $
         ((verts[*,2]-verts[*,1]) * lenFac)]
   endif
   ; Verts 1, 5.
   if ((verts[2,1]*verts[2,5]) le 0.0) then begin
      lenFac = verts[2,1] / (verts[2,1] - verts[2,5])
      newXYZ = [Temporary(newXYZ), verts[*,1] + $
         ((verts[*,5]-verts[*,1]) * lenFac)]
   endif
   ; Verts 2, 3.
   if ((verts[2,2]*verts[2,3]) le 0.0) then begin
      lenFac = verts[2,2] / (verts[2,2] - verts[2,3])
      newXYZ = [Temporary(newXYZ), verts[*,2] + $
         ((verts[*,3]-verts[*,2]) * lenFac)]
   endif
   ; Verts 2, 6.
   if ((verts[2,2]*verts[2,6]) le 0.0) then begin
      lenFac = verts[2,2] / (verts[2,2] - verts[2,6])
      newXYZ = [Temporary(newXYZ), verts[*,2] + $
         ((verts[*,6]-verts[*,2]) * lenFac)]
   endif
   ; Verts 3, 7.
   if ((verts[2,3]*verts[2,7]) le 0.0) then begin
      lenFac = verts[2,3] / (verts[2,3] - verts[2,7])
      newXYZ = [Temporary(newXYZ), verts[*,3] + $
         ((verts[*,7]-verts[*,3]) * lenFac)]
   endif
   ; Verts 4, 5.
   if ((verts[2,4]*verts[2,5]) le 0.0) then begin
      lenFac = verts[2,4] / (verts[2,4] - verts[2,5])
      newXYZ = [Temporary(newXYZ), verts[*,4] + $
         ((verts[*,5]-verts[*,4]) * lenFac)]
   endif
   ; Verts 4, 7.
   if ((verts[2,4]*verts[2,7]) le 0.0) then begin
      lenFac = verts[2,4] / (verts[2,4] - verts[2,7])
      newXYZ = [Temporary(newXYZ), verts[*,4] + $
         ((verts[*,7]-verts[*,4]) * lenFac)]
   endif
   ; Verts 5, 6.
   if ((verts[2,5]*verts[2,6]) le 0.0) then begin
      lenFac = verts[2,5] / (verts[2,5] - verts[2,6])
      newXYZ = [Temporary(newXYZ), verts[*,5] + $
         ((verts[*,6]-verts[*,5]) * lenFac)]
   endif
   ; Verts 6, 7.
   if ((verts[2,6]*verts[2,7]) le 0.0) then begin
      lenFac = verts[2,6] / (verts[2,6] - verts[2,7])
      newXYZ = [Temporary(newXYZ), verts[*,6] + $
         ((verts[*,7]-verts[*,6]) * lenFac)]
   endif

   if (N_ELEMENTS(newXYZ) le 3L) then return

   newXYZ = newXYZ[3:*]
   nPoints = N_ELEMENTS(newXYZ) / 3
   newXYZ = REFORM(TEMPORARY(newXYZ), 3, nPoints)

   ; newXYZ is now a 3,n array of 3D intersection vertices.

   ; Sort the vertices into counter-clockwise order.

   newXMin = MIN(newXYZ[0,*], MAX=newXMax)
   newYMin = MIN(newXYZ[1,*], MAX=newYMax)
   cX = (newXMin + newXMax) / 2.0
   cY = (newYMin + newYMax) / 2.0

   angDiff = ATAN(newXYZ[1,*]-cY, newXYZ[0,*]-cX)

   negInd = WHERE(angDiff lt 0.0)
   if (negInd[0] ge 0L) then angDiff[negInd] = angDiff[negInd] + (2.0*!PI)
   sortInd = SORT(Temporary(angDiff))
   newXYZ = newXYZ[*, Temporary(sortInd)]

   ; Save the intersection vertices on the image (oblique) plane.
   vertPlane = newXYZ
   ; Save the intersection vertices as 3D data coordinates.
   newXYZ = VERT_T3D(newXYZ, MATRIX=vTrans2, /NO_COPY)
   vert3D = newXYZ
   ; vertPlane and vert3D are returned to the calling procedure via
   ; the VERT_PLANE and VERT_3D keywords.

   ; Draw the intersection outline (optionally filled).
   if not(KEYWORD_SET(skipFill)) then $
      POLYFILL, newXYZ, /NORMAL, /T3D, COLOR=fillColor
   PLOTS, newXYZ, /NORMAL, /T3D, COLOR=edgeColor
   PLOTS, newXYZ[*,0], /NORMAL, /T3D, COLOR=edgeColor, /CONTINUE

   if (N_ELEMENTS(normColor) gt 0L) then begin
      ; Draw the surface normal vector.
      sliceNormU = obliqNormal
      sliceNormU[0] = obliqNormal[0] * maxDim / rangeX
      sliceNormU[1] = obliqNormal[1] * maxDim / rangeY
      sliceNormU[2] = obliqNormal[2] * maxDim / zDim

      normPoint = obliqCenter + sliceNormU

      if (normPoint[0] lt 0.0) then begin
         lenFac = obliqCenter[0] / (obliqCenter[0] - normPoint[0])
         normPoint = obliqCenter + (sliceNormU * lenFac)
      endif
      if (normPoint[0] gt 1.0) then begin
         lenFac = (1.0 - obliqCenter[0]) / (normPoint[0] - obliqCenter[0])
         normPoint = obliqCenter + (sliceNormU * lenFac)
      endif

      if (normPoint[1] lt 0.0) then begin
         lenFac = obliqCenter[1] / (obliqCenter[1] - normPoint[1])
         normPoint = obliqCenter + (sliceNormU * lenFac)
      endif
      if (normPoint[1] gt 1.0) then begin
         lenFac = (1.0 - obliqCenter[1]) / (normPoint[1] - obliqCenter[1])
         normPoint = obliqCenter + (sliceNormU * lenFac)
      endif

      if (normPoint[2] lt 0.0) then begin
         lenFac = obliqCenter[2] / (obliqCenter[2] - normPoint[2])
         normPoint = obliqCenter + (sliceNormU * lenFac)
      endif
      if (normPoint[2] gt 1.0) then begin
         lenFac = (1.0 - obliqCenter[2]) / (normPoint[2] - obliqCenter[2])
         normPoint = obliqCenter + (sliceNormU * lenFac)
      endif

      PLOTS, [obliqCenter[0], normPoint[0]], $
             [obliqCenter[1], normPoint[1]], $
             [obliqCenter[2], normPoint[2]], $
         /NORMAL, /T3D, COLOR=normColor
   endif

end
;******************************************************************************


;******************************************************************************
; Procedure to draw the cube and current slicing plane outline in the
; small slice status window.
pro Viz3D_SliceShow, sMainState

   COMPILE_OPT hidden, idl2

   fillColor = Viz3D_TransColor(sMainState, sMainState.sSliceState.fillColor)
   edgeColor = Viz3D_TransColor(sMainState, sMainState.sSliceState.edgeColor)
   normColor = Viz3D_TransColor(sMainState, sMainState.sSliceState.normColor)
   backColor = Viz3D_TransColor(sMainState, sMainState.backColor)

   WSET, sMainState.sSliceState.sliceWin
   ERASE

   case (sMainState.sSliceState.planeMode*sMainState.sSliceState.orthoDir) of
      0: begin ; Oblique
         Viz3D_DrawSliceOblique, sMainState.sSliceState.obliqCenter, $
            sMainState.sSliceState.obliqNormal, fillColor, edgeColor, $
            sMainState.sViewState.xMax, sMainState.sViewState.yMax, $
            sMainState.sViewState.zMax, sMainState.sViewState.zScale, $
            NORMCOLOR=normColor
      end
      1: begin ; X
         POLYFILL, sMainState.sSliceState.orthoPos, [0,1,1,0], [0,0,1,1], $
            /NORMAL, /T3D, COLOR=fillColor
         PLOTS, sMainState.sSliceState.orthoPos, [0,1,1,0,0], [0,0,1,1,0], $
            /NORMAL, /T3D, COLOR=edgeColor
      end
      2: begin ; Y
         POLYFILL, [0,1,1,0], sMainState.sSliceState.orthoPos, [0,0,1,1], $
            /NORMAL, /T3D, COLOR=fillColor
         PLOTS, [0,1,1,0,0], sMainState.sSliceState.orthoPos, [0,0,1,1,0], $
            /NORMAL, /T3D, COLOR=edgeColor
      end
      3: begin ; Z
         POLYFILL, [0,1,1,0], [0,0,1,1], sMainState.sSliceState.orthoPos, $
            /NORMAL, /T3D, COLOR=fillColor
         PLOTS, [0,1,1,0,0], [0,0,1,1,0], sMainState.sSliceState.orthoPos, $
            /NORMAL, /T3D, COLOR=edgeColor
      end
   endcase

   ; Draw the cube outline in the current window.
   Viz3D_DrawCube, sMainState, /SKIPBACK, /SKIPAXIS, /DIRECT

   WSET, sMainState.mainWin
end
;******************************************************************************


;******************************************************************************
; Procedure to draw the cube outline in the current window,
pro Viz3D_DrawCube, sMainState, SKIPCUBE=skipCube, SKIPAXIS=skipAxis, $
    SKIPFRONT=skipFront, SKIPBACK=skipBack, DIRECT=directDraw, AXIS=drawAxis

   COMPILE_OPT hidden, idl2

   axisOn = sMainState.axisOn
   if (KEYWORD_SET(drawAxis)) then axisOn = 1

   cubeColor = Viz3D_TransColor(sMainState, sMainState.cubeColor)
   axisColor = Viz3D_TransColor(sMainState, sMainState.axisColor)

   if ((axisOn) and not(KEYWORD_SET(skipAxis))) then $
      lineColor = axisColor else lineColor = cubeColor

   verts = FLTARR(3, 8, /NOZERO)
   verts[0, 0] = [0,0,0]
   verts[0, 1] = [1,0,0]
   verts[0, 2] = [1,1,0]
   verts[0, 3] = [0,1,0]
   verts[0, 4] = [0,0,1]
   verts[0, 5] = [1,0,1]
   verts[0, 6] = [1,1,1]
   verts[0, 7] = [0,1,1]
   verts = VERT_T3D(verts, /NO_COPY)

   cubeDrawn = 0
   if (((sMainState.cubeOn) and not(KEYWORD_SET(skipCube))) or $
      KEYWORD_SET(directDraw)) then begin
      ; Draw the cube.

      cubeDrawn = 1
      lCol = [lineColor, lineColor, cubeColor, cubeColor, lineColor]

      if not(KEYWORD_SET(skipBack)) then begin
         ; Draw the "hidden" lines as dotted.

         cp = CROSSP(verts[*,1]-verts[*,0], verts[*,4]-verts[*,0])
         hideFace0154 = (cp[2] lt 0.0)
         cp = CROSSP(verts[*,4]-verts[*,0], verts[*,3]-verts[*,0])
         hideFace0473 = (cp[2] lt 0.0)
         cp = CROSSP(verts[*,3]-verts[*,0], verts[*,1]-verts[*,0])
         hideFace0321 = (cp[2] lt 0.0)
         cp = CROSSP(verts[*,2]-verts[*,6], verts[*,7]-verts[*,6])
         hideFace6237 = (cp[2] lt 0.0)
         cp = CROSSP(verts[*,5]-verts[*,6], verts[*,2]-verts[*,6])
         hideFace6514 = (cp[2] lt 0.0)
         cp = CROSSP(verts[*,7]-verts[*,6], verts[*,5]-verts[*,6])
         hideFace6745 = (cp[2] lt 0.0)

         if (hideFace0154 and hideFace0321) then $
            PLOTS, verts[*, [0,1]], /NORMAL, T3D=0, COLOR=lineColor, $
               LINESTYLE=1
         if (hideFace0473 and hideFace0321) then $
            PLOTS, verts[*, [0,3]], /NORMAL, T3D=0, COLOR=lineColor, $
               LINESTYLE=1
         if (hideFace0154 and hideFace0473) then $
            PLOTS, verts[*, [0,4]], /NORMAL, T3D=0, COLOR=lineColor, $
               LINESTYLE=1

         if (hideFace0321 and hideFace6514) then $
            PLOTS, verts[*, [1,2]], /NORMAL, T3D=0, COLOR=cubeColor, $
               LINESTYLE=1
         if (hideFace0321 and hideFace6237) then $
            PLOTS, verts[*, [2,3]], /NORMAL, T3D=0, COLOR=cubeColor, $
               LINESTYLE=1
         if (hideFace0154 and hideFace6514) then $
            PLOTS, verts[*, [1,5]], /NORMAL, T3D=0, COLOR=cubeColor, $
               LINESTYLE=1
         if (hideFace0473 and hideFace6237) then $
            PLOTS, verts[*, [3,7]], /NORMAL, T3D=0, COLOR=cubeColor, $
               LINESTYLE=1
         if (hideFace6745 and hideFace0473) then $
            PLOTS, verts[*, [4,7]], /NORMAL, T3D=0, COLOR=cubeColor, $
               LINESTYLE=1
         if (hideFace6745 and hideFace0154) then $
            PLOTS, verts[*, [4,5]], /NORMAL, T3D=0, COLOR=cubeColor, $
               LINESTYLE=1
         if (hideFace6745 and hideFace6514) then $
            PLOTS, verts[*, [5,6]], /NORMAL, T3D=0, COLOR=cubeColor, $
               LINESTYLE=1
         if (hideFace6745 and hideFace6237) then $
            PLOTS, verts[*, [6,7]], /NORMAL, T3D=0, COLOR=cubeColor, $
               LINESTYLE=1
         if (hideFace6514 and hideFace6237) then $
            PLOTS, verts[*, [2,6]], /NORMAL, T3D=0, COLOR=cubeColor, $
               LINESTYLE=1
      endif

      if not(KEYWORD_SET(skipFront)) then begin
         ; Draw the remaining cube edges as solid (linestyle 0).

         cp = CROSSP(verts[*,1]-verts[*,0], verts[*,4]-verts[*,0])
         if (cp[2] gt 0.0) then $
            PLOTS, verts[*, [0,1,5,4,0]], /NORMAL, T3D=0, COLOR=lCol

         cp = CROSSP(verts[*,4]-verts[*,0], verts[*,3]-verts[*,0])
         if (cp[2] gt 0.0) then $
            PLOTS, verts[*, [0,4,7,3,0]], /NORMAL, T3D=0, COLOR=lCol

         cp = CROSSP(verts[*,3]-verts[*,0], verts[*,1]-verts[*,0])
         if (cp[2] gt 0.0) then $
            PLOTS, verts[*, [0,3,2,1,0]], /NORMAL, T3D=0, COLOR=lCol

         cp = CROSSP(verts[*,7]-verts[*,6], verts[*,5]-verts[*,6])
         if (cp[2] gt 0.0) then $
            PLOTS, verts[*, [6,7,4,5,6]], /NORMAL, T3D=0, COLOR=cubeColor

         cp = CROSSP(verts[*,5]-verts[*,6], verts[*,2]-verts[*,6])
         if (cp[2] gt 0.0) then $
            PLOTS, verts[*, [6,5,1,2,6]], /NORMAL, T3D=0, COLOR=cubeColor

         cp = CROSSP(verts[*,2]-verts[*,6], verts[*,7]-verts[*,6])
         if (cp[2] gt 0.0) then $
            PLOTS, verts[*, [6,2,3,7,6]], /NORMAL, T3D=0, COLOR=cubeColor
      endif
   endif

   if (((axisOn) and not(KEYWORD_SET(skipAxis))) and $
      (not(KEYWORD_SET(skipFront)) and not(KEYWORD_SET(directDraw)))) $
      then begin
      ; Draw the axis.

      if (cubeDrawn eq 0) then begin
         PLOTS, verts[*, [0,1]], /NORMAL, T3D=0, COLOR=axisColor
         PLOTS, verts[*, [0,3]], /NORMAL, T3D=0, COLOR=axisColor
         PLOTS, verts[*, [0,4]], /NORMAL, T3D=0, COLOR=axisColor
      endif

      cp = CROSSP(verts[*,5]-verts[*,1], verts[*,0]-verts[*,1])
      showPlane1 = (cp[2] gt 0.0)
      cp = CROSSP(verts[*,0]-verts[*,1], verts[*,2]-verts[*,1])
      showPlane2 = (cp[2] gt 0.0)
      cp = CROSSP(verts[*,2]-verts[*,1], verts[*,5]-verts[*,1])
      showPlane3 = (cp[2] gt 0.0)
      if ((showPlane1+showPlane2+showPlane3) ge 1) then begin
         PLOTS, [0.95,1.0,0.95], [0.01, 0.0, -0.01], [0,0,0], /NORMAL, $
            /T3D, COLOR=axisColor
         PLOTS, [0.95,1.0,0.95], [0,0,0], [0.01, 0.0, -0.01], /NORMAL, $
            /T3D, COLOR=axisColor
         XYOUTS, 1.05, 0.0, Z=0.0, 'X', /NORMAL, /T3D, $
            CHARSIZE=24.0/FLOAT(!D.Y_Ch_Size), COLOR=axisColor, $
            ALIGNMENT=0.5, TEXT_AXES=2
      endif

      cp = CROSSP(verts[*,0]-verts[*,3], verts[*,7]-verts[*,3])
      showPlane1 = (cp[2] gt 0.0)
      cp = CROSSP(verts[*,7]-verts[*,3], verts[*,2]-verts[*,3])
      showPlane2 = (cp[2] gt 0.0)
      cp = CROSSP(verts[*,2]-verts[*,3], verts[*,0]-verts[*,3])
      showPlane3 = (cp[2] gt 0.0)
      if ((showPlane1+showPlane2+showPlane3) ge 1) then begin
         PLOTS, [0.01, 0.0, -0.01], [0.95,1.0,0.95], [0,0,0], /NORMAL, $
            /T3D, COLOR=axisColor
         PLOTS, [0,0,0], [0.95,1.0,0.95], [0.01, 0.0, -0.01], /NORMAL, $
            /T3D, COLOR=axisColor
         XYOUTS, 0.0, 1.05, Z=0.0, 'Y', /NORMAL, /T3D, $
            CHARSIZE=24.0/FLOAT(!D.Y_Ch_Size), COLOR=axisColor, $
            ALIGNMENT=0.5, TEXT_AXES=1
      endif

      cp = CROSSP(verts[*,5]-verts[*,4], verts[*,7]-verts[*,4])
      showPlane1 = (cp[2] gt 0.0)
      cp = CROSSP(verts[*,7]-verts[*,4], verts[*,0]-verts[*,4])
      showPlane2 = (cp[2] gt 0.0)
      cp = CROSSP(verts[*,0]-verts[*,4], verts[*,5]-verts[*,4])
      showPlane3 = (cp[2] gt 0.0)
      if ((showPlane1+showPlane2+showPlane3) ge 1) then begin
         PLOTS, [0,0,0], [0.01, 0.0, -0.01], [0.95,1.0,0.95], /NORMAL, $
            /T3D, COLOR=axisColor
         PLOTS, [0.01, 0.0, -0.01], [0,0,0], [0.95,1.0,0.95], /NORMAL, $
            /T3D, COLOR=axisColor
         XYOUTS, 0.0, 0.0, Z=1.05, 'Z', /NORMAL, /T3D, $
            CHARSIZE=24.0/FLOAT(!D.Y_Ch_Size), COLOR=axisColor, $
            ALIGNMENT=0.5, TEXT_AXES=0
      endif
   endif

   EMPTY

end
;******************************************************************************


;******************************************************************************
pro Viz3D_DrawData, sMainState

   COMPILE_OPT hidden, idl2

   SET_PLOT, 'Z'
   Viz3D_DrawCube, sMainState, /SKIPFRONT
   img = TVRD()
   SET_PLOT, sMainState.screenDevice
   WSET, sMainState.pixWin
   TV, TEMPORARY(img)
   Viz3D_DrawCube, sMainState, /SKIPBACK
   EMPTY
   WSET, sMainState.mainWin
   DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, 0, 0, $
                 sMainState.pixWin]
   sMainState.cleanupView = 0
   sMainState.cleanupBuffer = 0

end
;******************************************************************************


;******************************************************************************
; Procedure to draw the block outline.
pro Viz3D_BlockShow, sMainState, DIRECT=directDraw

   COMPILE_OPT hidden, idl2

   if not(KEYWORD_SET(directDraw)) then $
      WSET, sMainState.sBlockState.blockWin

   x1 = sMainState.sBlockState.c1[0]
   y1 = sMainState.sBlockState.c1[1]
   z1 = sMainState.sBlockState.c1[2]
   x2 = sMainState.sBlockState.c2[0]
   y2 = sMainState.sBlockState.c2[1]
   z2 = sMainState.sBlockState.c2[2]
   blockColor = $
      Viz3D_TransColor(sMainState, sMainState.sBlockState.blockColor)

   ; Draw the cube outline in the current window.
   if not(KEYWORD_SET(directDraw)) then begin
      ERASE
      Viz3D_DrawCube, sMainState, /DIRECT, /AXIS
   endif else begin

      c1Color = Viz3D_TransColor(sMainState, sMainState.sBlockState.c1Color)
      c2Color = Viz3D_TransColor(sMainState, sMainState.sBlockState.c2Color)
      xM = sMainState.sViewState.xMax
      yM = sMainState.sViewState.yMax
      zM = sMainState.sViewState.zMax

      norm = [[x1,y1,z1], [x1,y1,z1-1]]
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) gt 0.0) then begin
         PLOTS, [x1,x1], [y1,y1], [z1, 0], /DATA, /T3D, COLOR=c1Color
         PLOTS, [x1,x1], [y1,y1], [z1,zM], /DATA, /T3D, COLOR=c1Color, $
            LINESTYLE=1
      endif else begin
         PLOTS, [x1,x1], [y1,y1], [z1, 0], /DATA, /T3D, COLOR=c1Color, $
            LINESTYLE=1
         PLOTS, [x1,x1], [y1,y1], [z1,zM], /DATA, /T3D, COLOR=c1Color
      endelse
      norm = [[x1,y1,z1], [x1,y1-1,z1]]
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) gt 0.0) then begin
         PLOTS, [x1,x1], [y1, 0], [z1,z1], /DATA, /T3D, COLOR=c1Color
         PLOTS, [x1,x1], [y1,yM], [z1,z1], /DATA, /T3D, COLOR=c1Color, $
            LINESTYLE=1
      endif else begin
         PLOTS, [x1,x1], [y1, 0], [z1,z1], /DATA, /T3D, COLOR=c1Color, $
            LINESTYLE=1
         PLOTS, [x1,x1], [y1,yM], [z1,z1], /DATA, /T3D, COLOR=c1Color
      endelse
      norm = [[x1,y1,z1], [x1-1,y1,z1]]
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) gt 0.0) then begin
         PLOTS, [x1, 0], [y1,y1], [z1,z1], /DATA, /T3D, COLOR=c1Color
         PLOTS, [x1,xM], [y1,y1], [z1,z1], /DATA, /T3D, COLOR=c1Color, $
            LINESTYLE=1
      endif else begin
         PLOTS, [x1, 0], [y1,y1], [z1,z1], /DATA, /T3D, COLOR=c1Color, $
            LINESTYLE=1
         PLOTS, [x1,xM], [y1,y1], [z1,z1], /DATA, /T3D, COLOR=c1Color
      endelse

      norm = [[x1,y1,z1], [x1,y1,z1-1]]
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) gt 0.0) then begin
         PLOTS, [x2,x2], [y2,y2], [z2, 0], /DATA, /T3D, COLOR=c2Color
         PLOTS, [x2,x2], [y2,y2], [z2,zM], /DATA, /T3D, COLOR=c2Color, $
            LINESTYLE=1
      endif else begin
         PLOTS, [x2,x2], [y2,y2], [z2, 0], /DATA, /T3D, COLOR=c2Color, $
            LINESTYLE=1
         PLOTS, [x2,x2], [y2,y2], [z2,zM], /DATA, /T3D, COLOR=c2Color
      endelse
      norm = [[x1,y1,z1], [x1,y1-1,z1]]
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) gt 0.0) then begin
         PLOTS, [x2,x2], [y2, 0], [z2,z2], /DATA, /T3D, COLOR=c2Color
         PLOTS, [x2,x2], [y2,yM], [z2,z2], /DATA, /T3D, COLOR=c2Color, $
            LINESTYLE=1
      endif else begin
         PLOTS, [x2,x2], [y2, 0], [z2,z2], /DATA, /T3D, COLOR=c2Color, $
            LINESTYLE=1
         PLOTS, [x2,x2], [y2,yM], [z2,z2], /DATA, /T3D, COLOR=c2Color
      endelse
      norm = [[x1,y1,z1], [x1-1,y1,z1]]
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) gt 0.0) then begin
         PLOTS, [x2, 0], [y2,y2], [z2,z2], /DATA, /T3D, COLOR=c2Color
         PLOTS, [x2,xM], [y2,y2], [z2,z2], /DATA, /T3D, COLOR=c2Color, $
            LINESTYLE=1
      endif else begin
         PLOTS, [x2, 0], [y2,y2], [z2,z2], /DATA, /T3D, COLOR=c2Color, $
            LINESTYLE=1
         PLOTS, [x2,xM], [y2,y2], [z2,z2], /DATA, /T3D, COLOR=c2Color
      endelse

   endelse

   PLOTS, [x1,x2,x2,x1,x1], [y1,y1,y2,y2,y1], [z1,z1,z1,z1,z1], $
      /DATA, /T3D, COLOR=blockColor
   PLOTS, [x1,x2,x2,x1,x1], [y1,y1,y2,y2,y1], [z2,z2,z2,z2,z2], $
      /DATA, /T3D, COLOR=blockColor
   PLOTS, [x1,x1], [y1,y1], [z1,z2], /DATA, /T3D, COLOR=blockColor
   PLOTS, [x2,x2], [y1,y1], [z1,z2], /DATA, /T3D, COLOR=blockColor
   PLOTS, [x2,x2], [y2,y2], [z1,z2], /DATA, /T3D, COLOR=blockColor
   PLOTS, [x1,x1], [y2,y2], [z1,z2], /DATA, /T3D, COLOR=blockColor

   EMPTY

   if not(KEYWORD_SET(directDraw)) then $
      WSET, sMainState.mainWin

end
;******************************************************************************


;******************************************************************************
; Procedure to draw the cube in the small view window.
pro Viz3D_ViewShow, sMainState

   COMPILE_OPT hidden, idl2

   WSET, sMainState.sViewState.viewWin

   ; Draw the cube outline in the current window.
   ERASE
   Viz3D_DrawCube, sMainState, /DIRECT, /AXIS

   WSET, sMainState.mainWin

end
;******************************************************************************


;******************************************************************************
; Procedure to draw the histogram and threshold value in the
; small iso-surface window.
pro Viz3D_SurfShow, sMainState, CALC_HIST=calcHist

   COMPILE_OPT hidden, idl2

   WSET, sMainState.sSurfState.surfWin

   tColor = Viz3D_TransColor(sMainState, sMainState.sSurfState.tColor)
   histColor = Viz3D_TransColor(sMainState, sMainState.sSurfState.histColor)
   axisColor = Viz3D_TransColor(sMainState, sMainState.sSurfState.axisColor)

   saveP = !P
   saveX = !X
   saveY = !Y
   saveZ = !Z

   x1 = 0.03
   y1 = 0.05
   x2 = 0.97
   y2 = 0.95

   lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
   hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
   surfThresh = *(sMainState.sSurfState.hSurfThresh)
   surfThresh[sMainState.curData] = $
      (surfThresh[sMainState.curData] > lTh) < hTh
   sT = surfThresh[sMainState.curData]
   *(sMainState.sSurfState.hSurfThresh) = surfThresh

   tPos = (sT - lTh) / (hTh - lTh)
   tPos = (tPos * (x2-x1)) + x1

   lRangeHist = *(sMainState.sSurfState.hRangeHist)
   rangeHist = *(lRangeHist[sMainState.curData])

   if (KEYWORD_SET(calcHist)) then begin
      data3D = Viz3D_GetData(sMainState)
      bSize = (hTh - lTh) / 200.0
      n_samples = N_ELEMENTS(data3D)
      IF (n_samples GT 32767L) THEN BEGIN
         n_samples = 32767L + ROUND(SQRT(n_samples - 32767L))
         s_index = RANDOMU(s, 1)
         s_index = RANDOMU(s, n_samples)
         s_index = LONG(TEMPORARY(s_index) * FLOAT(n_samples))
         hist_data = FLOAT(data3D[TEMPORARY(s_index)])
         rangeHist = $
            HISTOGRAM(TEMPORARY(hist_data), MIN=lTh, MAX=hTh, $
               BINSIZE=bSize) > 1.0
      ENDIF ELSE BEGIN
         rangeHist = $
            HISTOGRAM(FLOAT(data3D), MIN=lTh, MAX=hTh, $
               BINSIZE=bSize) > 1.0
      ENDELSE
      rangeHist = ALOG(FLOAT(TEMPORARY(rangeHist))) > 0.0
      Viz3D_PutData, data3D, sMainState

      PLOT, rangeHist, T3D=0, /DATA, XSTYLE=5, YSTYLE=5, /NODATA, $
         POSITION=[x1,y1,x2,y2], MIN_VALUE=1.0
      PLOTS, [x1,x2,x2,x1,x1], [y1,y1,y2,y2,y1], T3D=0, $
         /NORMAL, COLOR=axisColor
      OPLOT, rangeHist, T3D=0, COLOR=histColor, MIN_VALUE=1.0, PSYM=3

      WSET, sMainState.sSurfState.surfPix
      DEVICE, COPY=[0, 0, 192, 128, 0, 0, sMainState.sSurfState.surfWin]
      WSET, sMainState.sSurfState.surfWin

   endif else begin
      PLOT, rangeHist, T3D=0, /DATA, XSTYLE=5, YSTYLE=5, /NODATA, $
         POSITION=[x1,y1,x2,y2], MIN_VALUE=1.0, /NOERASE
      DEVICE, COPY=[0, 0, 192, 128, 0, 0, sMainState.sSurfState.surfPix]
   endelse

   PLOTS, [tPos, tPos], [y1, y2], T3D=0, /NORMAL, COLOR=tColor
   EMPTY

   *(lRangeHist[sMainState.curData]) = rangeHist
   *(sMainState.sSurfState.hRangeHist) = lRangeHist

   WIDGET_CONTROL, sMainState.sSurfState.wSurfText, $
      SET_VALUE=STRTRIM(STRING(sT),2)

   !P = TEMPORARY(saveP)
   !X = TEMPORARY(saveX)
   !Y = TEMPORARY(saveY)
   !Z = TEMPORARY(saveZ)

   WSET, sMainState.mainWin
end
;******************************************************************************


;******************************************************************************
; Procedure to draw the histogram and threshold values in the
; small threshold window.
pro Viz3D_ThreshShow, sMainState, DYNAMIC=dynUpdate

   COMPILE_OPT hidden, idl2

   WSET, sMainState.sThreshState.threshWin
   lowColor = Viz3D_TransColor(sMainState, sMainState.sThreshState.lowColor)
   highColor = Viz3D_TransColor(sMainState, sMainState.sThreshState.highColor)
   transColor = $
      Viz3D_TransColor(sMainState, sMainState.sThreshState.transColor)
   histColor = Viz3D_TransColor(sMainState, sMainState.sThreshState.histColor)
   axisColor = Viz3D_TransColor(sMainState, sMainState.sThreshState.axisColor)
   backColor = Viz3D_TransColor(sMainState, 0)

   saveP = !P
   saveX = !X
   saveY = !Y
   saveZ = !Z

   x1 = 0.03
   y1 = 0.05
   x2 = 0.97
   y2 = 0.95

   lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
   hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
   vTp = (*(sMainState.sThreshState.hTransVal))[sMainState.curData]

   minD = (*(sMainState.sThreshState.hMinData))[sMainState.curData]
   maxD = (*(sMainState.sThreshState.hMaxData))[sMainState.curData]

   lowPos = (lTh - minD) / (maxD - minD)
   lowPos = (lowPos * (x2-x1)) + x1
   highPos = (hTh - minD) / (maxD - minD)
   highPos = (highPos * (x2-x1)) + x1

   transPos = (vTp - minD) / (maxD - minD)
   transPos = (transPos * (x2-x1)) + x1

   lDataHist = *(sMainState.sThreshState.hDataHist)
   DataHist = *(lDataHist[sMainState.curData])

   if (KEYWORD_SET(dynUpdate)) then begin
      PLOT, dataHist, T3D=0, /DATA, XSTYLE=5, YSTYLE=5, /NODATA, $
         POSITION=[x1,y1,x2,y2], MIN_VALUE=1.0, /NOERASE
      DEVICE, COPY=[0, 0, 192, 128, 0, 0, sMainState.sThreshState.threshPix]
   endif else begin
      PLOT, dataHist, T3D=0, /DATA, XSTYLE=5, YSTYLE=5, /NODATA, $
         POSITION=[x1,y1,x2,y2], MIN_VALUE=1.0
      PLOTS, [x1,x2,x2,x1,x1], [y1,y1,y2,y2,y1], T3D=0, /NORMAL, COLOR=axisColor
      OPLOT, dataHist, T3D=0, COLOR=histColor, MIN_VALUE=1.0, PSYM=3

      WSET, sMainState.sThreshState.threshPix
      DEVICE, COPY=[0, 0, 192, 128, 0, 0, sMainState.sThreshState.threshWin]
      WSET, sMainState.sThreshState.threshWin

   endelse

   PLOTS, [transPos, transPos], [y1, y2], T3D=0, /NORMAL, COLOR=transColor
   PLOTS, [transPos, transPos], [y1, y2], T3D=0, /NORMAL, COLOR=backColor, $
          LINESTYLE=1
   PLOTS, [lowPos, lowPos], [y1, y2], T3D=0, /NORMAL, COLOR=lowColor
   PLOTS, [highPos, highPos], [y1, y2], T3D=0, /NORMAL, COLOR=highColor


   WIDGET_CONTROL, sMainState.sThreshState.wThreshLowText, $
      SET_VALUE=STRTRIM(STRING(lTh),2)
   WIDGET_CONTROL, sMainState.sThreshState.wThreshHighText, $
      SET_VALUE=STRTRIM(STRING(hTh),2)
   WIDGET_CONTROL, sMainState.sThreshState.wThreshTransText, $
      SET_VALUE=STRTRIM(STRING(vTp),2)

   !P = TEMPORARY(saveP)
   !X = TEMPORARY(saveX)
   !Y = TEMPORARY(saveY)
   !Z = TEMPORARY(saveZ)

   WSET, sMainState.mainWin
end
;******************************************************************************


;******************************************************************************
; Procedure to plot profile data in the small profile window.
pro Viz3D_ProfShow, sMainState, DIRECT=directDraw

   COMPILE_OPT hidden, idl2

   lineColor = Viz3D_TransColor(sMainState, sMainState.sProfState.lineColor)
   axisColor = Viz3D_TransColor(sMainState, sMainState.sProfState.axisColor)
   markColor1 = Viz3D_TransColor(sMainState, sMainState.sProfState.markColor1)
   markColor2 = Viz3D_TransColor(sMainState, sMainState.sProfState.markColor2)

   data3D = Viz3D_GetData(sMainState)

   if (sMainState.sProfState.profType eq 0) then begin ; Orthogonal profile.
      case sMainState.sProfState.profDir of
         1: begin
               profData = data3D[sMainState.sProfState.x1Ortho, $
                               sMainState.sProfState.y1Ortho, *]
               yData = FINDGEN(sMainState.szData[3])
            end
         2: begin
               profData = data3D[sMainState.sProfState.x1Ortho, *, $
                               sMainState.sProfState.z1Ortho]
               yData = FINDGEN(sMainState.szData[2])
            end
         3: begin
               profData = data3D[*, sMainState.sProfState.y1Ortho, $
                               sMainState.sProfState.z1Ortho]
               yData = FINDGEN(sMainState.szData[1])
            end
      endcase
   endif else begin ; Oblique profile.
      samples = MAX(sMainState.szData[1:3]) * 2
      yData = FINDGEN(samples)
      xInter = yData / FLOAT(samples - 1)
      yInter = xInter
      zInter = xInter
      xInter = ((sMainState.sProfState.x2Obliq - $
                 sMainState.sProfState.x1Obliq) * $
                TEMPORARY(xInter)) + sMainState.sProfState.x1Obliq
      yInter = ((sMainState.sProfState.y2Obliq - $
                 sMainState.sProfState.y1Obliq) * $
                TEMPORARY(yInter)) + sMainState.sProfState.y1Obliq
      zInter = ((sMainState.sProfState.z2Obliq - $
                 sMainState.sProfState.z1Obliq) * $
                TEMPORARY(zInter)) + sMainState.sProfState.z1Obliq
      profData = INTERPOLATE(data3D, TEMPORARY(xInter), $
                    TEMPORARY(yInter), TEMPORARY(zInter))
   endelse

   Viz3D_PutData, data3D, sMainState

   saveP = !P
   saveX = !X
   saveY = !Y
   saveZ = !Z

   maxY = N_ELEMENTS(yData) - 1

   lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
   hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]

   if not(KEYWORD_SET(directDraw)) then begin
      WSET, sMainState.sProfState.profPix
      PLOT, profData, yData, TICKLEN=(1), XSTYLE=1, YSTYLE=5, $
         XRANGE=[lTh, hTh], YRANGE=[0, maxY], XMARGIN=[1,1], YMARGIN=[2,1], $
         /NODATA, COLOR=axisColor, CHARSIZE=0.5
      PLOTS, [lTh,lTh], [0,maxY], /DATA, T3D=0, COLOR=axisColor
      PLOTS, [hTh,hTh], [0,maxY], /DATA, T3D=0, COLOR=axisColor
      PLOTS, [lTh,hTh], [0,0], /DATA, T3D=0, COLOR=markColor1, THICK=2
      PLOTS, [lTh,hTh], [maxY,maxY], /DATA, T3D=0, COLOR=markColor2, THICK=2
   endif
   WSET, sMainState.sProfState.profWin
   DEVICE, COPY=[0, 0, 192, sMainState.sProfState.profWinY, 0, 0, $
                 sMainState.sProfState.profPix]

   PLOT, TEMPORARY(profData), TEMPORARY(yData), TICKLEN=(1), XMARGIN=[1,1], $
      YMARGIN=[2,1], /NOERASE, COLOR=lineColor, XSTYLE=5, YSTYLE=5, $
      XRANGE=[lTh, hTh], YRANGE=[0, maxY], CHARSIZE=0.5, /DATA, T3D=0
   EMPTY

   !P = TEMPORARY(saveP)
   !X = TEMPORARY(saveX)
   !Y = TEMPORARY(saveY)
   !Z = TEMPORARY(saveZ)

   WSET, sMainState.mainWin
end
;******************************************************************************


;******************************************************************************
; Function to return the transparent value given a threshold percent.
; Compensate for the differential shading bands using planeDir.
function Viz3D_TranspValu, sMainState, planeDir
   COMPILE_OPT hidden, idl2

   lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
   hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
   vTp = (*(sMainState.sThreshState.hTransVal))[sMainState.curData]
   transVal = (vTp - lTh) / (hTh - lTh)
   transVal = (transVal * FLOAT(sMainState.sColorState.nColor-1)) > 0.0

   return, ROUND(transVal) + (planeDir * sMainState.sColorState.nColor)
end
;******************************************************************************


;******************************************************************************
; Add a new graphic to the display list.
function Viz3D_Add_Graphic, sMainState, graphic, NO_COPY=noCopy

   COMPILE_OPT hidden, idl2

   gType = TAG_NAMES(graphic, /STRUCTURE_NAME)
   case gType of
      'VIZ3D_ORTHO_PLANE': begin
         gName = 'Slice: Orthogonal, '
         case graphic.orthoDir of
            1: begin
               gName = TEMPORARY(gName) + 'X, ' + $
               STRTRIM(STRING(ROUND(graphic.orthoPos * $
                  sMainState.sViewState.xMax)), 2)
            end
            2: begin
               gName = TEMPORARY(gName) + 'Y, ' + $
               STRTRIM(STRING(ROUND(graphic.orthoPos * $
                  sMainState.sViewState.yMax)), 2)
            end
            3: begin
               gName = TEMPORARY(gName) + 'Z, ' + $
               STRTRIM(STRING(ROUND(graphic.orthoPos * $
                  sMainState.sViewState.zMax)), 2)
            end
         endcase
      end
      'VIZ3D_OBLIQ_PLANE': begin
         gName = 'Slice: Oblique'
      end
      'VIZ3D_BLOCK': begin
         gName = 'Block: '
         case graphic.blockMode of
            0: gName = TEMPORARY(gName) + 'Subract, ('
            1: gName = TEMPORARY(gName) + 'Add, ('
         endcase
         gName = TEMPORARY(gName) + STRTRIM(STRING(graphic.c1[0]), 2) + ','
         gName = TEMPORARY(gName) + STRTRIM(STRING(graphic.c1[1]), 2) + ','
         gName = TEMPORARY(gName) + STRTRIM(STRING(graphic.c1[2]), 2) + '), ('
         gName = TEMPORARY(gName) + STRTRIM(STRING(graphic.c2[0]), 2) + ','
         gName = TEMPORARY(gName) + STRTRIM(STRING(graphic.c2[1]), 2) + ','
         gName = TEMPORARY(gName) + STRTRIM(STRING(graphic.c2[2]), 2) + ')'
      end
      'VIZ3D_SURF': begin
         gName = 'Surface: '
         case graphic.surfSide of
            0: gName = TEMPORARY(gName) + 'Low, '
            1: gName = TEMPORARY(gName) + 'High, '
         endcase
         gName = TEMPORARY(gName) + STRTRIM(STRING(graphic.surfThresh), 2)
      end
      'VIZ3D_PROJ': begin
         gName = 'Projection: '
         case graphic.projType of
            0: gName = TEMPORARY(gName) + 'Max, '
            1: gName = TEMPORARY(gName) + 'Avg, '
         endcase
         case graphic.projReso of
            0: gName = TEMPORARY(gName) + 'Low'
            1: gName = TEMPORARY(gName) + 'Med'
            2: gName = TEMPORARY(gName) + 'High'
         endcase
      end
   endcase

   wDeleteBttn = WIDGET_BUTTON(sMainState.wDeleteMenu, VALUE=gName, $
                               UVALUE='wDeleteBttn')

   p_graphic = PTR_NEW(graphic, NO_COPY=KEYWORD_SET(noCopy))
   ; Add p_graphic to the list
   if (PTR_VALID(sMainState.hDisplayList)) then begin
      if (N_ELEMENTS(*(sMainState.hDisplayList)) GT 0) then begin
         *(sMainState.hDisplayList) = [*(sMainState.hDisplayList), p_graphic]
      endif else begin
         *(sMainState.hDisplayList) = [p_graphic]
      endelse
   endif
   return, p_graphic

end
;******************************************************************************


;******************************************************************************
; Procedure to draw the data on an orthogonal slicing plane.
pro Viz3D_OrthoPlaneDraw, sMainState, SKIP_ADD=skipAdd, SKIP_DRAW=skipDraw

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, sMainState.wStatText, $
      SET_VALUE=('Drawing orthogonal plane ...')

   sliceDir = sMainState.sSliceState.orthoDir
   slicePos = sMainState.sSliceState.orthoPos

   ; Check out the data without making a copy.
   data3D = Viz3D_GetData(sMainState)

   case sliceDir of
      1: begin ; X
         pPos = ROUND(slicePos * sMainState.sViewState.xMax)
         pPos = pPos < sMainState.sViewState.xMax
         dataPlane = REFORM(data3D[pPos, *, *])
         x = REPLICATE(slicePos, 4)
         y = [0.0,1.0,1.0,0.0]
         z = [0.0,0.0,1.0,1.0]
         pDim1 = sMainState.sViewState.yMax
         pDim2 = sMainState.sViewState.zMax
      end
      2: begin ; Y
         pPos = ROUND(slicePos * sMainState.sViewState.yMax)
         pPos = pPos < sMainState.sViewState.yMax
         dataPlane = REFORM(data3D[*, pPos, *])
         x = [0.0,1.0,1.0,0.0]
         y = REPLICATE(slicePos, 4)
         z = [0.0,0.0,1.0,1.0]
         pDim1 = sMainState.sViewState.xMax
         pDim2 = sMainState.sViewState.zMax
      end
      3: begin ; Z
         pPos = ROUND(slicePos * sMainState.sViewState.zMax)
         pPos = pPos < sMainState.sViewState.zMax
         dataPlane = REFORM(data3D[*, *, pPos])
         x = [0.0,1.0,1.0,0.0]
         y = [0.0,0.0,1.0,1.0]
         z = REPLICATE(slicePos, 4)
         pDim1 = sMainState.sViewState.xMax
         pDim2 = sMainState.sViewState.yMax
      end
   endcase

   ; Check the data back in again.
   Viz3D_PutData, data3D, sMainState

   ; Extract the data on the orthogonal plane.
   dataPlane = Viz3D_ScaleData(Temporary(dataPlane), sMainState)

   ; Scale the data to the current threshold.
   dataPlane = BYTSCL(TEMPORARY(dataPlane), MIN=0B, MAX=255B, $
                      TOP=sMainState.sColorState.nColor-1)

   ; Shift the data into the appropriate differential shading
   ; portion of the color table.
   dataPlane = TEMPORARY(dataPlane) + $
      BYTE((sliceDir-1) * sMainState.sColorState.nColor)

   ; Draw the slice in the Z buffer.

   SET_PLOT, 'Z'

   if (sMainState.sSliceState.drawMode) then begin ; Expose mode
      tempImage = TVRD()
      tempDepth = TVRD(/WORDS, CHANNEL=1)
      ERASE
      POLYFILL, x, y, z, PATTERN=dataPlane, /T3D, /NORMAL, $
         IMAGE_INTERP=sMainState.interpMode, $
         TRANSPARENT=Viz3D_TranspValu(sMainState, sliceDir-1), $
         IMAGE_COORD=[[0,0], [pDim1,0], [pDim1,pDim2], [0,pDim2]]
      planeDepth = TVRD(/WORDS, CHANNEL=1)
      planeIndex = WHERE(TEMPORARY(planeDepth) gt (-32765), nPlane)
      IF (nPlane GT 0) THEN tempDepth[TEMPORARY(planeIndex)] = (-32765)
      planeDepth = 0
      TV, TEMPORARY(tempDepth), /WORDS, CHANNEL=1
      TV, TEMPORARY(tempImage)
   endif

   POLYFILL, x, y, z, PATTERN=dataPlane, /T3D, /NORMAL, $
      IMAGE_INTERP=sMainState.interpMode, $
      TRANSPARENT=Viz3D_TranspValu(sMainState, sliceDir-1), $
      IMAGE_COORD=[[0,0], [pDim1,0], [pDim1,pDim2], [0,pDim2]]

   SET_PLOT, sMainState.screenDevice

   ; Update the big view window.
   if (NOT(KEYWORD_SET(skipDraw))) then $
      Viz3D_DrawData, sMainState

   ; Add orthogonal plane to sMainState.hDisplayList
   if (NOT(KEYWORD_SET(skipAdd))) then begin
      lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
      hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
      vTp = (*(sMainState.sThreshState.hTransVal))[sMainState.curData]
      graphic = {VIZ3D_ORTHO_PLANE, curData:sMainState.curData, $
                 orthoDir:sMainState.sSliceState.orthoDir, $
                 orthoPos:sMainState.sSliceState.orthoPos, $
                 drawMode:sMainState.sSliceState.drawMode, $
                 lowThresh:lTh, highThresh:hTh, transVal:vTp}
      h = Viz3D_Add_Graphic(sMainState, graphic, /NO_COPY)
   endif

   WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=('')

end
;******************************************************************************


;******************************************************************************
; Procedure to draw the data on an oblique slicing plane.
pro Viz3D_ObliqPlaneDraw, sMainState, vertPlane, vert3D, SKIP_ADD=skipAdd, $
       SKIP_DRAW=skipDraw

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, sMainState.wStatText, $
      SET_VALUE=('Drawing oblique plane ...')

   sliceNormal = sMainState.sSliceState.obliqNormal
   sliceCenter = sMainState.sSliceState.obliqCenter

   ; Compute the maximum necessary slicing plane image dimension.
   maxLen = LONG(SQRT(sMainState.sViewState.xMax^2 + $
                      sMainState.sViewState.yMax^2 + $
                      sMainState.sViewState.zMax^2))

   ; Compute the amount necessary to shift the slicing plane to the center.
   maxXvert = MAX(vertPlane[0,*], MIN=minXvert)
   maxYvert = MAX(vertPlane[1,*], MIN=minYvert)
   shiftX = (maxXvert + minXvert) / 2.0
   shiftY = (maxYvert + minYvert) / 2.0

   maxP = SQRT(3.0) ; Slicing rectangle interpolation points
                    ; range between 0 and SQRT(3) in X and Y
                    ; (before shift and transformation).

   ; Compute the shifted interpolation points on the plane.
   ; (Normalized coordinates, with no rotation or translation).
   planePts = FLTARR(3, maxLen^2, /NOZERO)
   sF = FLOAT(maxLen-1L)
   planePts[0, *] = shiftX + $
      ((((FINDGEN(maxLen) # REPLICATE(1.0, maxLen)) / sF) - 0.5) * maxP)
   planePts[1, *] = shiftY + $
      ((((REPLICATE(1.0, maxLen) # FINDGEN(maxLen)) / sF) - 0.5) * maxP)
   planePts[2, *] = 0.0

   ; Scale the surface normal vector.
   zDim = sMainState.sViewState.zMax * sMainState.sViewState.zScale
   maxDim = sMainState.sViewState.xMax > sMainState.sViewState.yMax > zDim
   sliceNormU = sliceNormal
   sliceNormU[0] = sliceNormal[0] * sMainState.sViewState.xMax / maxDim
   sliceNormU[1] = sliceNormal[1] * sMainState.sViewState.yMax / maxDim
   sliceNormU[2] = sliceNormal[2] * zDim / maxDim

   ; Compute the rotations and transformation matrix.

   if ((sliceNormU[0] eq 0.0) and (sliceNormU[1] eq 0.0)) then angZ = 0.0 $
   else angZ = ATAN(sliceNormU[1], sliceNormU[0])

   pDistance = SQRT(sliceNormU[0]^2 + sliceNormU[1]^2)
   if ((pDistance eq 0.0) and (sliceNormU[2] eq 0.0)) then angY = 0.0 $
   else angY = ATAN(sliceNormU[2], pDistance)

   sZ = SIN(angZ)
   cZ = COS(angZ)
   sY = SIN((!PI/2.0)-angY)
   cY = COS((!PI/2.0)-angY)

   ident4 = FLTARR(4, 4)
   ident4[[0,5,10,15]] = 1.0

   vTrans = ident4
   ; Y rotation.
   m4X4 = ident4
   m4x4[0,0] = cY
   m4x4[0,2] = (-sY)
   m4x4[2,0] = sY
   m4x4[2,2] = cY
   vTrans = TEMPORARY(vTrans) # m4X4
   ; Z rotation.
   m4X4 = ident4
   m4x4[0,0] = cZ
   m4x4[0,1] = sZ
   m4x4[1,0] = (-sZ)
   m4x4[1,1] = cZ
   vTrans = TEMPORARY(vTrans) # m4X4
   ; Translation to the slice center.
   m4X4 = ident4
   m4X4[[3,7,11]] = (+sliceCenter)
   vTrans = TEMPORARY(vTrans) # m4X4

   ; Transform the plane points.
   planePts = VERT_T3D(planePts, MATRIX=vTrans, /NO_COPY)
   planePts[0, *] = planePts[0, *] * Float(sMainState.sViewState.xMax)
   planePts[1, *] = planePts[1, *] * Float(sMainState.sViewState.yMax)
   planePts[2, *] = planePts[2, *] * Float(sMainState.sViewState.zMax)

   ; Check out the data without making a copy.
   data3D = Viz3D_GetData(sMainState)

   ; Interpolate the data on the oblique plane using the plane points,
   dataPlane = $
      Interpolate(data3D, planePts[0,*], planePts[1,*], planePts[2,*])

   ; Check the data back in.
   Viz3D_PutData, data3D, sMainState

   ; Free the memory used by planePts.
   planePts = 0

   ; Scale the oblique plane data using the current thresholds.
   dataPlane = Viz3D_ScaleData(Temporary(dataPlane), sMainState)

   ; Scale the oblique plane data into the appropriate part of the color table
   ; (for differential shading).
   dataPlane = BYTSCL(TEMPORARY(dataPlane), MIN=0B, MAX=255B, $
                      TOP=sMainState.sColorState.nColor-1)
   maxDir = MAX(ABS(sliceNormU), maxInd)
   dataPlane = TEMPORARY(dataPlane) + $
      BYTE(maxInd * sMainState.sColorState.nColor)

   ; Reform dataPlane from a one dimensional array of (maxLen^2) elements
   ; to a 2D array with dimensions (maxLen, maxLen).
   dataPlane = REFORM(TEMPORARY(dataPlane), maxLen, maxLen)

   ; USE the Z buffer.

   SET_PLOT, 'Z'

   ; Compute the image coordinates corresponding to the planar cube/plane
   ; intersection points.

   s_vertPlane = vertPlane[0:1, *]
   s_vertPlane[0,*] = (s_vertPlane[0,*] / maxP) + 0.5 - (shiftX / maxP)
   s_vertPlane[1,*] = (s_vertPlane[1,*] / maxP) + 0.5 - (shiftY / maxP)
   s_vertPlane = TEMPORARY(s_vertPlane) * sF

   if (sMainState.sSliceState.drawMode) then begin ; Expose mode
      tempImage = TVRD()
      tempDepth = TVRD(/WORDS, CHANNEL=1)
      ERASE
      POLYFILL, vert3D, PATTERN=dataPlane, /T3D, /NORMAL, $
         IMAGE_INTERP=sMainState.interpMode, $
         TRANSPARENT=Viz3D_TranspValu(sMainState, maxInd), $
         IMAGE_COORD=s_vertPlane
      planeDepth = TVRD(/WORDS, CHANNEL=1)
      planeIndex = WHERE(planeDepth ge (-32765))
      if (planeIndex[0] ge 0L) then begin
         tempDepth[planeIndex] = planeDepth[planeIndex]
      endif
      planeDepth = 0
      TV, TEMPORARY(tempDepth), /WORDS, CHANNEL=1
      TV, TEMPORARY(tempImage)
   endif

   ; Draw the data on the oblique plane.
   POLYFILL, vert3D, PATTERN=dataPlane, /T3D, /NORMAL, $
      IMAGE_INTERP=sMainState.interpMode, $
      TRANSPARENT=Viz3D_TranspValu(sMainState, maxInd), $
      IMAGE_COORD=s_vertPlane

   SET_PLOT, sMainState.screenDevice

   ; Update the big view window.
   if (NOT(KEYWORD_SET(skipDraw))) then $
      Viz3D_DrawData, sMainState

   ; Add oblique plane to sMainState.hDisplayList
   if (NOT(KEYWORD_SET(skipAdd))) then begin
      lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
      hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
      vTp = (*(sMainState.sThreshState.hTransVal))[sMainState.curData]
      graphic = {VIZ3D_OBLIQ_PLANE, curData:sMainState.curData, $
                 hVertPlane:PTR_NEW(), hVert3D:PTR_NEW(), $
                 drawMode:sMainState.sSliceState.drawMode, $
                 obliqNormal:sMainState.sSliceState.obliqNormal, $
                 obliqCenter:sMainState.sSliceState.obliqCenter, $
                 lowThresh:lTh, highThresh:hTh, transVal:vTp}
      h = Viz3D_Add_Graphic(sMainState, graphic, /NO_COPY)
      graphic = *h
      graphic.hVertPlane = PTR_NEW(vertPlane, /NO_COPY)
      graphic.hVert3D = PTR_NEW(vert3D, /NO_COPY)
      *h = graphic
   endif

   WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=('')
end
;******************************************************************************


;******************************************************************************
; Procedure to draw the data on a block.
pro Viz3D_BlockDraw, sMainState, SKIP_ADD=skipAdd, SKIP_DRAW=skipDraw

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, sMainState.wStatText, $
      SET_VALUE=('Drawing block ...')

   x1 = FLOAT(sMainState.sBlockState.c1[0] < sMainState.sBlockState.c2[0])
   x2 = FLOAT(sMainState.sBlockState.c1[0] > sMainState.sBlockState.c2[0])
   y1 = FLOAT(sMainState.sBlockState.c1[1] < sMainState.sBlockState.c2[1])
   y2 = FLOAT(sMainState.sBlockState.c1[1] > sMainState.sBlockState.c2[1])
   z1 = FLOAT(sMainState.sBlockState.c1[2] < sMainState.sBlockState.c2[2])
   z2 = FLOAT(sMainState.sBlockState.c1[2] > sMainState.sBlockState.c2[2])

   ; Return if any block dimension is zero.
   if (((x1 eq x2) or (y1 eq y2)) or (z1 eq z2)) then return

   ; Check out the data without making a copy.
   data3D = Viz3D_GetData(sMainState)

   SET_PLOT, 'Z'

   if (sMainState.sBlockState.blockMode) then begin ; Add mode.
      dim1 = (x2 - x1)
      dim2 = (y2 - y1)
      faceDir = 2
      subImg = Viz3D_ScaleData(REFORM(data3D[x1:x2, y1:y2, z1]), sMainState)
      subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                      TOP=sMainState.sColorState.nColor-1)
      subImg = TEMPORARY(subImg) + $
                  BYTE(faceDir * sMainState.sColorState.nColor)
      POLYFILL, [x1,x2,x2,x1], [y1,y1,y2,y2], [z1,z1,z1,z1], PATTERN=subImg, $
         /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
         TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
         IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]
      subImg = Viz3D_ScaleData(REFORM(data3D[x1:x2, y1:y2, z2]), sMainState)
      subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                      TOP=sMainState.sColorState.nColor-1)
      subImg = TEMPORARY(subImg) + $
                  BYTE(faceDir * sMainState.sColorState.nColor)
      POLYFILL, [x1,x2,x2,x1], [y1,y1,y2,y2], [z2,z2,z2,z2], PATTERN=subImg, $
         /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
         TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
         IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]

      dim1 = (x2 - x1)
      dim2 = (z2 - z1)
      faceDir = 1
      subImg = Viz3D_ScaleData(REFORM(data3D[x1:x2, y1, z1:z2]), sMainState)
      subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                      TOP=sMainState.sColorState.nColor-1)
      subImg = TEMPORARY(subImg) + $
                  BYTE(faceDir * sMainState.sColorState.nColor)
      POLYFILL, [x1,x2,x2,x1], [y1,y1,y1,y1], [z1,z1,z2,z2], PATTERN=subImg, $
         /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
         TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
         IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]
      subImg = Viz3D_ScaleData(REFORM(data3D[x1:x2, y2, z1:z2]), sMainState)
      subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                      TOP=sMainState.sColorState.nColor-1)
      subImg = TEMPORARY(subImg) + $
                  BYTE(faceDir * sMainState.sColorState.nColor)
      POLYFILL, [x1,x2,x2,x1], [y2,y2,y2,y2], [z1,z1,z2,z2], PATTERN=subImg, $
         /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
         TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
         IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]

      dim1 = (y2 - y1)
      dim2 = (z2 - z1)
      faceDir = 0
      subImg = Viz3D_ScaleData(REFORM(data3D[x1, y1:y2, z1:z2]), sMainState)
      subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                      TOP=sMainState.sColorState.nColor-1)
      subImg = TEMPORARY(subImg) + $
                  BYTE(faceDir * sMainState.sColorState.nColor)
      POLYFILL, [x1,x1,x1,x1], [y1,y2,y2,y1], [z1,z1,z2,z2], PATTERN=subImg, $
         /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
         TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
         IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]
      subImg = Viz3D_ScaleData(REFORM(data3D[x2, y1:y2, z1:z2]), sMainState)
      subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                      TOP=sMainState.sColorState.nColor-1)
      subImg = TEMPORARY(subImg) + $
                  BYTE(faceDir * sMainState.sColorState.nColor)
      POLYFILL, [x2,x2,x2,x2], [y1,y2,y2,y1], [z1,z1,z2,z2], PATTERN=subImg, $
         /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
         TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
         IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]


   endif else begin ; Subtract mode.
      dims = FLOAT([sMainState.sViewState.xMax, sMainState.sViewState.yMax, $
                    sMainState.sViewState.zMax])
      dims = [[dims], [dims]]

      imageBuffer1 = TVRD()
      depthBuffer1 = TVRD(CHANNEL=1, /WORDS)
      POLYFILL, [x1,x2,x2,x1], [y1,y1,y2,y2], [z1,z1,z1,z1], /T3D, /DATA
      POLYFILL, [x1,x2,x2,x1], [y1,y1,y2,y2], [z2,z2,z2,z2], /T3D, /DATA
      POLYFILL, [x1,x2,x2,x1], [y1,y1,y1,y1], [z1,z1,z2,z2], /T3D, /DATA
      POLYFILL, [x1,x2,x2,x1], [y2,y2,y2,y2], [z1,z1,z2,z2], /T3D, /DATA
      POLYFILL, [x1,x1,x1,x1], [y1,y2,y2,y1], [z1,z1,z2,z2], /T3D, /DATA
      POLYFILL, [x2,x2,x2,x2], [y1,y2,y2,y1], [z1,z1,z2,z2], /T3D, /DATA
      depthMask1 = (TVRD(CHANNEL=1, /WORDS) gt depthBuffer1)

      ERASE

      norm = [[x1,y1,z1], [x1,y1,z1-1.0]] / dims
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) lt 0.0) then begin
         dim1 = (x2 - x1)
         dim2 = (y2 - y1)
         faceDir = 2
         subImg = $
            Viz3D_ScaleData(REFORM(data3D[x1:x2, y1:y2, z1]), sMainState)
         subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                         TOP=sMainState.sColorState.nColor-1)
         subImg = TEMPORARY(subImg) + $
                     BYTE(faceDir * sMainState.sColorState.nColor)
         POLYFILL, [x1,x2,x2,x1], [y1,y1,y2,y2], [z1,z1,z1,z1], $
            PATTERN=subImg, /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
            TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
            IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]
      endif

      norm = [[x1,y1,z1], [x1,y1-1.0,z1]] / dims
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) lt 0.0) then begin
         dim1 = (x2 - x1)
         dim2 = (z2 - z1)
         faceDir = 1
         subImg = $
            Viz3D_ScaleData(REFORM(data3D[x1:x2, y1, z1:z2]), sMainState)
         subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                         TOP=sMainState.sColorState.nColor-1)
         subImg = TEMPORARY(subImg) + $
                     BYTE(faceDir * sMainState.sColorState.nColor)
         POLYFILL, [x1,x2,x2,x1], [y1,y1,y1,y1], [z1,z1,z2,z2], $
            PATTERN=subImg, /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
            TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
            IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]
      endif

      norm = [[x1,y1,z1], [x1-1.0,y1,z1]] / dims
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) lt 0.0) then begin
         dim1 = (y2 - y1)
         dim2 = (z2 - z1)
         faceDir = 0
         subImg = $
            Viz3D_ScaleData(REFORM(data3D[x1, y1:y2, z1:z2]), sMainState)
         subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                         TOP=sMainState.sColorState.nColor-1)
         subImg = TEMPORARY(subImg) + $
                     BYTE(faceDir * sMainState.sColorState.nColor)
         POLYFILL, [x1,x1,x1,x1], [y1,y2,y2,y1], [z1,z1,z2,z2], $
            PATTERN=subImg, /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
            TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
            IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]
      endif

      norm = [[x2,y2,z2], [x2,y2,z2+1.0]] / dims
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) lt 0.0) then begin
         dim1 = (x2 - x1)
         dim2 = (y2 - y1)
         faceDir = 2
         subImg = $
            Viz3D_ScaleData(REFORM(data3D[x1:x2, y1:y2, z2]), sMainState)
         subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                         TOP=sMainState.sColorState.nColor-1)
         subImg = TEMPORARY(subImg) + $
                     BYTE(faceDir * sMainState.sColorState.nColor)
         POLYFILL, [x1,x2,x2,x1], [y1,y1,y2,y2], [z2,z2,z2,z2], $
            PATTERN=subImg, /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
            TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
            IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]
      endif

      norm = [[x2,y2,z2], [x2,y2+1.0,z2]] / dims
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) lt 0.0) then begin
         dim1 = (x2 - x1)
         dim2 = (z2 - z1)
         faceDir = 1
         subImg = $
            Viz3D_ScaleData(REFORM(data3D[x1:x2, y2, z1:z2]), sMainState)
         subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                         TOP=sMainState.sColorState.nColor-1)
         subImg = TEMPORARY(subImg) + $
                     BYTE(faceDir * sMainState.sColorState.nColor)
         POLYFILL, [x1,x2,x2,x1], [y2,y2,y2,y2], [z1,z1,z2,z2], $
            PATTERN=subImg, /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
            TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
            IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]
      endif

      norm = [[x2,y2,z2], [x2+1.0,y2,z2]] / dims
      norm = VERT_T3D(norm, /NO_COPY)
      if ((norm[2,1]-norm[2,0]) lt 0.0) then begin
         dim1 = (y2 - y1)
         dim2 = (z2 - z1)
         faceDir = 0
         subImg = $
            Viz3D_ScaleData(REFORM(data3D[x2, y1:y2, z1:z2]), sMainState)
         subImg = BYTSCL(TEMPORARY(subImg), MIN=0B, MAX=255B, $
                         TOP=sMainState.sColorState.nColor-1)
         subImg = TEMPORARY(subImg) + $
                     BYTE(faceDir * sMainState.sColorState.nColor)
         POLYFILL, [x2,x2,x2,x2], [y1,y2,y2,y1], [z1,z1,z2,z2], $
            PATTERN=subImg, /T3D, /DATA, IMAGE_INTERP=sMainState.interpMode, $
            TRANSPARENT=Viz3D_TranspValu(sMainState, faceDir), $
            IMAGE_COORD=[[0,0],[dim1,0],[dim1,dim2],[0,dim2]]
      endif

      imageBuffer2 = TVRD()
      depthBuffer2 = TVRD(CHANNEL=1, /WORDS)
      depthMask2 = (depthBuffer2 gt (-32765)) and $
                   (depthBuffer2 lt depthBuffer1)
      depthMask = WHERE(TEMPORARY(depthMask1) * TEMPORARY(depthMask2))

      if (depthMask[0] ge 0L) then begin
         imageBuffer1[depthMask] = imageBuffer2[depthMask]
         depthBuffer1[depthMask] = depthBuffer2[depthMask]
      endif

      TV, imageBuffer1
      TV, depthBuffer1, CHANNEL=1, /WORDS

   endelse

   ; Put the data back.
   Viz3D_PutData, data3D, sMainState

   SET_PLOT, sMainState.screenDevice

   ; Update the big view window.
   if (NOT(KEYWORD_SET(skipDraw))) then $
      Viz3D_DrawData, sMainState

   ; Add block to sMainState.hDisplayList
   if (NOT(KEYWORD_SET(skipAdd))) then begin
      lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
      hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
      vTp = (*(sMainState.sThreshState.hTransVal))[sMainState.curData]
      graphic = {VIZ3D_BLOCK, curData:sMainState.curData, $
                 c1:sMainState.sBlockState.c1, $
                 c2:sMainState.sBlockState.c2, $
                 blockMode:sMainState.sBlockState.blockMode, $
                 lowThresh:lTh, highThresh:hTh, transVal:vTp}
      h = Viz3D_Add_Graphic(sMainState, graphic, /NO_COPY)
   endif

   WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=('')
end
;******************************************************************************


;******************************************************************************
; Procedure to draw an iso-surface.
pro Viz3D_SurfDraw, sMainState, hVertList, hPolyList, hColorList, $
       SKIP_ADD=skipAdd, SKIP_DRAW=skipDraw, CALC_SURF=calcSurf

   COMPILE_OPT hidden, idl2

   if (KEYWORD_SET(calcSurf)) then begin
      WIDGET_CONTROL, sMainState.wStatText, $
         SET_VALUE=('Calculating iso-surface ...')

      ; Check out the data without making a copy.
      data3D = Viz3D_GetData(sMainState)

      surfThresh = *(sMainState.sSurfState.hSurfThresh)
      sT = surfThresh[sMainState.curData]
      SHADE_VOLUME, data3D, sT, vertList, polyList, $
                    LOW=(sMainState.sSurfState.surfSide eq 0)
      Viz3D_PutData, data3D, sMainState

      if (N_ELEMENTS(polyList) le 0L) then begin ; No iso-surface to render.
         WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
         return
      endif

      if (polyList[0] lt 0L) then begin ; No iso-surface to render.
         WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
         return
      endif

      if (sMainState.curData eq sMainState.sSurfState.curShade) then begin
         ; Light-source shading.
         colorList = (-1L)
      endif else begin
         data3D = $
            Viz3D_GetData(sMainState, sMainState.sSurfState.curShade)
         shadeData = data3D
         Viz3D_PutData, data3D, sMainState, sMainState.sSurfState.curShade
         colorList = INTERPOLATE(TEMPORARY(shadeData), $
            vertList[0,*], vertList[1,*], vertList[2,*], MISSING=0)
         lowThresh = *(sMainState.sThreshState.hLowThresh)
         highThresh = *(sMainState.sThreshState.hHighThresh)
         lTh = lowThresh[sMainState.sSurfState.curShade]
         hTh = highThresh[sMainState.sSurfState.curShade]
         colorList = BYTSCL(TEMPORARY(colorList), MIN=lTh, MAX=hTh, $
            TOP=sMainState.sColorState.nColor-1)
         colorList = TEMPORARY(colorList) + $
            BYTE(3 * sMainState.sColorState.nColor)
      endelse

      WIDGET_CONTROL, sMainState.wStatText, $
         SET_VALUE=(STRTRIM((SIZE(vertList))[2],2) + ' Verts, ' + $
         STRTRIM((SIZE(polyList))[1]/4,2) + ' Polys.')
      hVertList = PTR_NEW(vertList, /NO_COPY)
      hPolyList = PTR_NEW(polyList, /NO_COPY)
      hColorList = PTR_NEW(colorList, /NO_COPY)
   endif else begin
      WIDGET_CONTROL, sMainState.wStatText, $
         SET_VALUE=('Drawing iso-surface ...')
   endelse

   SET_PLOT, 'Z'

   SET_SHADING, /GOURAUD, LIGHT=[-0.5, 0.5, 0.5], REJECT=0, $
      VALUES=[(sMainState.sColorState.nColor*3), $
              (sMainState.sColorState.nColor*5)-1]

   if ((*hColorList)[0] lt 0B) then begin ; Light-source shading.
      nothing = POLYSHADE(*hVertList, *hPolyList, /DATA, /T3D, $
         TOP=((sMainState.sColorState.nColor*4)-1))
   endif else begin ; Data shading.
      nothing = POLYSHADE(*hVertList, *hPolyList, /DATA, /T3D, $
         SHADES=(*hColorList), TOP=((sMainState.sColorState.nColor*4)-1))
   endelse


   SET_PLOT, sMainState.screenDevice

   ; Update the big view window.
   if (NOT(KEYWORD_SET(skipDraw))) then $
      Viz3D_DrawData, sMainState

   ; Add surface to sMainState.hDisplayList
   if (NOT(KEYWORD_SET(skipAdd))) then begin
      sT = (*(sMainState.sSurfState.hSurfThresh))[sMainState.curData]
      graphic = {VIZ3D_SURF, curData:sMainState.curData, $
                 hVertList:PTR_NEW(/ALLOCATE_HEAP), $
                 hPolyList:PTR_NEW(/ALLOCATE_HEAP), $
                 hColorList:PTR_NEW(/ALLOCATE_HEAP), $
                 surfThresh:sT, surfSide:sMainState.sSurfState.surfSide}
      h = Viz3D_Add_Graphic(sMainState, graphic, /NO_COPY)
      if (KEYWORD_SET(calcSurf)) then begin
         graphic = *h
         *((*h).hVertList) = TEMPORARY(*hVertList)
         *((*h).hPolyList) = TEMPORARY(*hPolyList)
         *((*h).hColorList) = TEMPORARY(*hColorList)
         PTR_FREE, hVertList
         PTR_FREE, hPolyList
         PTR_FREE, hColorList
      endif
   endif

   WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=('')
end
;******************************************************************************


;******************************************************************************
; Procedure to draw a projection.
pro Viz3D_ProjDraw, sMainState, SKIP_ADD=skipAdd, SKIP_DRAW=skipDraw

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, sMainState.wStatText, $
      SET_VALUE=('Calculating projection ...')

   SET_PLOT, 'Z'
   imageBuffer = TVRD()
   depthBuffer = TVRD(CHANNEL=1, /WORDS)

   annoIndex = WHERE(imageBuffer gt (5*sMainState.sColorState.nColor))
   if (annoIndex[0] ge 0L) then depthBuffer[TEMPORARY(annoIndex)] = (-32765)

   minDim = sMainState.sViewState.xMax < sMainState.sViewState.yMax < $
            sMainState.sViewState.zMax
   maxDim = sMainState.sViewState.xMax > sMainState.sViewState.yMax > $
            sMainState.sViewState.zMax
   avgDim = Float(maxDim + minDim) / 2.0
   case sMainState.sProjState.projReso of
      0: begin ; Low.
         xS = ROUND(FLOAT(avgDim) / 2.0) > 2
         yS = ROUND(FLOAT(avgDim) / 2.0) > 2
         zS = ROUND(FLOAT(avgDim) / 2.0) > 2
      end
      1: begin ; Medium.
         xS = ROUND(FLOAT(avgDim) / 1.5) > 2
         yS = ROUND(FLOAT(avgDim) / 1.5) > 2
         zS = ROUND(FLOAT(avgDim) / 1.5) > 2
      end
      2: begin ; high.
         xS = ROUND(FLOAT(maxDim) / 1.0) > 2
         yS = ROUND(FLOAT(maxDim) / 1.0) > 2
         zS = ROUND(FLOAT(maxDim) / 1.0) > 2
      end
   endcase

   ; Calculate the projection.

   data3D_raw = Viz3D_GetData(sMainState)
   data3D = data3D_raw
   Viz3D_PutData, data3D_raw, sMainState
   data3D = Viz3D_ScaleData(data3D, sMainState)

   xyS = xS * yS

   cubePts = FLTARR(8,4)
   cubePts[0,*] = [0,0,0,1]
   cubePts[1,*] = [1,0,0,1]
   cubePts[2,*] = [1,1,0,1]
   cubePts[3,*] = [0,1,0,1]
   cubePts[4,*] = [0,0,1,1]
   cubePts[5,*] = [1,0,1,1]
   cubePts[6,*] = [1,1,1,1]
   cubePts[7,*] = [0,1,1,1]
   cubePts = cubepts # !P.T
   xMin = MIN(cubePts[*,0], MAX=xMax)
   yMin = MIN(cubePts[*,1], MAX=yMax)
   zMin = MIN(cubePts[*,2], MAX=zMax)
   zRange = zMax - zMin

   xInd = REFORM(((Findgen(xS) / Float(xS - 1L)) # Replicate(1.0, yS)), xyS)
   yInd = REFORM((Replicate(1.0, xS) # (Findgen(yS) / Float(yS - 1L))), xyS)

   index = FLTARR(xyS, 4, /NOZERO)
   index[0, 0] = TEMPORARY(xInd)
   index[0, 1] = TEMPORARY(yInd)
   index[*, 3] = 1.0

   kMax = zS - 1
   fkMax = FLOAT(kMax)

   pDepth = CONGRID(depthBuffer, Xs, Ys, /INTERP, /MINUS_ONE)
   if (sMainState.sProjState.projType) then $
      sumImage = FLTARR(xS, yS) $
   else sumImage = BYTARR(xS, yS)

   depthQ = (100.0 - sMainState.sProjState.depthQ) / 100.0
   for k=0, kMax do begin ; Project one plane at a time.

      fk = FLOAT(k)
      zVal = ((fk / fkMax) * zRange) + zMin
      index[*, 2] = zVal
      tIndex = index # sMainState.sViewState.invTrans

      indX = ((tIndex[*, 0] / tIndex[*, 3]) - !X.S[0]) / !X.S[1]
      indY = ((tIndex[*, 1] / tIndex[*, 3]) - !Y.S[0]) / !Y.S[1]
      indZ = ((tIndex[*, 2] / tIndex[*, 3]) - !Z.S[0]) / !Z.S[1]

      zPlane = ROUND(2.0 * ((((zVal - 0.5) > (-0.5)) < 0.5) * 32765.0))
      zInd = WHERE(pDepth lt zPlane)

      if (zInd[0] ge 0L) then begin ; Something new in front
         depthFac = BYTE((1.0 - ((zVal-zMin)/(zMax-zMin))) * depthQ * 255.0)
         tempImg = REFORM(((INTERPOLATE(data3D, indX, indY, indZ, $
                            MISSING=0B) > depthFac) - depthFac), xS, yS)

         lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
         hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
         vTp = (*(sMainState.sThreshState.hTransVal))[sMainState.curData]

         transVal = (FLOAT(vTp - lTh) / FLOAT(hTh - lTh)) * 255.0
         transVal = BYTE((transVal > 0.0) < 255.0)
         transInd = WHERE(tempImg lt transVal)
         if (transInd[0] ge 0L) then tempImg[TEMPORARY(transInd)] = 0B
         if (sMainState.sProjState.projType) then begin ; Avg projection.
            newVal = FLOAT(tempImg[zInd])
            setInd = WHERE(sumImage[zInd] lt newVal)
            sumImage[zInd] = sumImage[zInd] + (TEMPORARY(newVal) / fkMax)
            if (setInd[0] ge 0L) then begin
               setInd = zInd[TEMPORARY(setInd)]
               pDepth[TEMPORARY(setInd)] = zPlane
            endif
         endif else begin ; Max projection.
            setInd = WHERE(sumImage[zInd] lt tempImg[zInd])
            if (setInd[0] ge 0L) then begin
               setInd = zInd[TEMPORARY(setInd)]
               pDepth[TEMPORARY(setInd)] = zPlane
            endif
            sumImage = TEMPORARY(sumImage) > tempImg
         endelse
      endif
   endfor
   if (sMainState.sProjState.projType) then begin ; Scale the avg projection.
      minSum = MIN(sumImage, MAX=maxSum)
      if ((maxSum - minSum) gt 0.0) then $
         sumImage = (TEMPORARY(sumImage) - minSum) / (maxSum - minSum)
      minD = (*(sMainState.sThreshState.hMinData))[sMainState.curData]
      maxD = (*(sMainState.sThreshState.hMaxData))[sMainState.curData]
      sumImage = (TEMPORARY(sumImage) * (maxD - minD)) + minD
      sumImage = BYTSCL(TEMPORARY(sumImage), MIN=minD, MAX=maxD, $
         TOP=sMainState.sColorState.nColor-1)
   endif else begin
      sumImage = BYTSCL(TEMPORARY(sumImage), $
         MIN=0, MAX=255, TOP=sMainState.sColorState.nColor-1)
   endelse

   tempImg = 0
   index = 0
   tIndex = 0
   setInd = 0
   indX = 0
   indY = 0
   indZ = 0
   zInd = 0

   sBand = (FLOAT(!D.X_Size) / FLOAT(xS)) > (FLOAT(!D.Y_Size) / FLOAT(yS))
   sBand = ROUND(sBand / 2.0) > 3
   sumImage = CONGRID(TEMPORARY(sumImage), !D.X_Size, !D.Y_Size, $
                      /INTERP, /MINUS_ONE)
   sumImage = SMOOTH(TEMPORARY(sumImage), sBand, /EDGE)
   pDepth = CONGRID(TEMPORARY(pDepth), !D.X_Size, !D.Y_Size, $
                    /INTERP, /MINUS_ONE)
   pDepth = SMOOTH(TEMPORARY(pDepth), sBand, /EDGE)
   newInd = WHERE(pDepth ge depthBuffer)

   if (newInd[0] ge 0L) then begin
      sumImage = TEMPORARY(sumImage) + BYTE(sMainState.sColorState.nColor*4)
      sumImage = sumImage[newInd]
      pDepth = pDepth[newInd]
      imageBuffer[newInd] = TEMPORARY(sumImage)
      depthBuffer[TEMPORARY(newInd)] = TEMPORARY(pDepth)

      ERASE
      TV, TEMPORARY(imageBuffer)
      TV, TEMPORARY(depthBuffer), CHANNEL=1, /WORDS
   endif

   SET_PLOT, sMainState.screenDevice

   ; Update the big view window.
   if (NOT(KEYWORD_SET(skipDraw))) then $
      Viz3D_DrawData, sMainState

   ; Add projection to sMainState.hDisplayList
   if (NOT(KEYWORD_SET(skipAdd))) then begin
      lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
      hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
      vTp = (*(sMainState.sThreshState.hTransVal))[sMainState.curData]
      graphic = {VIZ3D_PROJ, curData:sMainState.curData, $
                 projType:sMainState.sProjState.projType, $
                 projReso:sMainState.sProjState.projReso, $
                 depthQ:sMainState.sProjState.depthQ, $
                 lowThresh:lTh, highThresh:hTh, transVal:vTp}
      h = Viz3D_Add_Graphic(sMainState, graphic, /NO_COPY)
   endif

   WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=('')
end
;******************************************************************************


;******************************************************************************
; Procedure to draw the probe in the big window.
pro Viz3D_ProbeDraw, sMainState

   COMPILE_OPT hidden, idl2

   lineColor = Viz3D_TransColor(sMainState, sMainState.sProbeState.lineColor)

   WSET, sMainState.mainWin
   DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, $
                 0, 0, sMainState.pixWin]

   PLOTS, [sMainState.sProbeState.x, sMainState.sProbeState.x], $
          [sMainState.sProbeState.y, sMainState.sProbeState.y], $
          [0, sMainState.szData[3]-1], $
          /DATA, /T3D, COLOR=lineColor

   PLOTS, [sMainState.sProbeState.x, sMainState.sProbeState.x], $
          [0, sMainState.szData[2]-1], $
          [sMainState.sProbeState.z, sMainState.sProbeState.z], $
          /DATA, /T3D, COLOR=lineColor

   PLOTS, [0, sMainState.szData[1]-1], $
          [sMainState.sProbeState.y, sMainState.sProbeState.y], $
          [sMainState.sProbeState.z, sMainState.sProbeState.z], $
          /DATA, /T3D, COLOR=lineColor
   EMPTY

   data3D = Viz3D_GetData(sMainState)
   dataPt = INTERPOLATE(data3D, sMainState.sProbeState.x, $
                                sMainState.sProbeState.y, $
                                sMainState.sProbeState.z)
   Viz3D_PutData, data3D, sMainState

   WIDGET_CONTROL, sMainState.wStatText, $
      SET_VALUE=('Data: ' + STRTRIM(STRING(FLOAT(dataPt)), 2))
   WIDGET_CONTROL, sMainState.sProbeState.wProbeXText, $
      SET_VALUE=STRTRIM(STRING(sMainState.sProbeState.x), 2)
   WIDGET_CONTROL, sMainState.sProbeState.wProbeYText, $
      SET_VALUE=STRTRIM(STRING(sMainState.sProbeState.y), 2)
   WIDGET_CONTROL, sMainState.sProbeState.wProbeZText, $
      SET_VALUE=STRTRIM(STRING(sMainState.sProbeState.z), 2)

end
;******************************************************************************


;******************************************************************************
; Procedure to draw the profile line in the big window.
pro Viz3D_ProfDraw, sMainState

   COMPILE_OPT hidden, idl2

   lineColor = Viz3D_TransColor(sMainState, sMainState.sProfState.lineColor)
   markColor1 = Viz3D_TransColor(sMainState, sMainState.sProfState.markColor1)
   markColor2 = Viz3D_TransColor(sMainState, sMainState.sProfState.markColor2)

   WSET, sMainState.mainWin
   DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, $
                 0, 0, sMainState.pixWin]
   if (sMainState.sProfState.profType) then begin
      if ((sMainState.sProfState.z2Obliq ne 0) and $
          (sMainState.sProfState.z2Obliq ne sMainState.szData[3]-1)) then $
         PLOTS, [sMainState.sProfState.x2Obliq, $
                 sMainState.sProfState.x2Obliq], $
                [sMainState.sProfState.y2Obliq, $
                 sMainState.sProfState.y2Obliq], $
                [0, sMainState.szData[3]-1], $
                /DATA, /T3D, COLOR=markColor2
      if ((sMainState.sProfState.y2Obliq ne 0) and $
          (sMainState.sProfState.y2Obliq ne sMainState.szData[2]-1)) then $
         PLOTS, [sMainState.sProfState.x2Obliq, $
                 sMainState.sProfState.x2Obliq], $
                [0, sMainState.szData[2]-1], $
                [sMainState.sProfState.z2Obliq, $
                 sMainState.sProfState.z2Obliq], $
                /DATA, /T3D, COLOR=markColor2
      if ((sMainState.sProfState.x2Obliq ne 0) and $
          (sMainState.sProfState.x2Obliq ne sMainState.szData[1]-1)) then $
         PLOTS, [0, sMainState.szData[1]-1], $
                [sMainState.sProfState.y2Obliq, $
                 sMainState.sProfState.y2Obliq], $
                [sMainState.sProfState.z2Obliq, $
                 sMainState.sProfState.z2Obliq], $
                /DATA, /T3D, COLOR=markColor2

      if ((sMainState.sProfState.z1Obliq ne 0) and $
          (sMainState.sProfState.z1Obliq ne sMainState.szData[3]-1)) then $
         PLOTS, [sMainState.sProfState.x1Obliq, $
                 sMainState.sProfState.x1Obliq], $
                [sMainState.sProfState.y1Obliq, $
                 sMainState.sProfState.y1Obliq], $
                [0, sMainState.szData[3]-1], $
                /DATA, /T3D, COLOR=markColor1
      if ((sMainState.sProfState.y1Obliq ne 0) and $
          (sMainState.sProfState.y1Obliq ne sMainState.szData[2]-1)) then $
         PLOTS, [sMainState.sProfState.x1Obliq, $
                 sMainState.sProfState.x1Obliq], $
                [0, sMainState.szData[2]-1], $
                [sMainState.sProfState.z1Obliq, $
                 sMainState.sProfState.z1Obliq], $
                /DATA, /T3D, COLOR=markColor1
      if ((sMainState.sProfState.x1Obliq ne 0) and $
          (sMainState.sProfState.x1Obliq ne sMainState.szData[1]-1)) then $
         PLOTS, [0, sMainState.szData[1]-1], $
                [sMainState.sProfState.y1Obliq, $
                 sMainState.sProfState.y1Obliq], $
                [sMainState.sProfState.z1Obliq, $
                 sMainState.sProfState.z1Obliq], $
                /DATA, /T3D, COLOR=markColor1

      PLOTS, [sMainState.sProfState.x1Obliq, sMainState.sProfState.x2Obliq], $
             [sMainState.sProfState.y1Obliq, sMainState.sProfState.y2Obliq], $
             [sMainState.sProfState.z1Obliq, sMainState.sProfState.z2Obliq], $
             /DATA, /T3D, COLOR=lineColor

      WIDGET_CONTROL, sMainState.wStatText, $
         SET_VALUE=('(' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.x1Obliq)), 2) + ', ' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.y1Obliq)), 2) + ', ' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.z1Obliq)), 2) + $
               '), (' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.x2Obliq)), 2) + ', ' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.y2Obliq)), 2) + ', ' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.z2Obliq)), 2) + ')')
   endif else begin
      PLOTS, [sMainState.sProfState.x1Ortho, sMainState.sProfState.x2Ortho], $
             [sMainState.sProfState.y1Ortho, sMainState.sProfState.y2Ortho], $
             [sMainState.sProfState.z1Ortho, sMainState.sProfState.z2Ortho], $
             /DATA, /T3D, COLOR=lineColor
      PLOTS, [sMainState.sProfState.x1Ortho], $
             [sMainState.sProfState.y1Ortho], $
             [sMainState.sProfState.z1Ortho], $
             /DATA, /T3D, COLOR=markColor1, PSYM=4, THICK=2
      PLOTS, [sMainState.sProfState.x2Ortho], $
             [sMainState.sProfState.y2Ortho], $
             [sMainState.sProfState.z2Ortho], $
             /DATA, /T3D, COLOR=markColor2, PSYM=4, THICK=2
      WIDGET_CONTROL, sMainState.wStatText, $
         SET_VALUE=('(' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.x1Ortho)), 2) + ', ' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.y1Ortho)), 2) + ', ' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.z1Ortho)), 2) + $
               '), (' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.x2Ortho)), 2) + ', ' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.y2Ortho)), 2) + ', ' + $
            STRTRIM(STRING(FIX(sMainState.sProfState.z2Ortho)), 2) + ')')
   endelse

   EMPTY

end
;******************************************************************************


;******************************************************************************
; Redraw everything in the display list.
pro Viz3D_Redraw, sMainState

   COMPILE_OPT hidden, idl2

   SET_PLOT, 'Z'
   ERASE

   displayListIndex = 0
   if (PTR_VALID(sMainState.hDisplayList)) then begin
      if (N_ELEMENTS(*(sMainState.hDisplayList)) GT 0) then begin
         hID = (*(sMainState.hDisplayList))[0]  ; get first in list
      endif  else hID = PTR_NEW() ;null pointer, not valid
   endif else hID = PTR_NEW() ;null pointer, not valid
   while (PTR_VALID(hID)) do begin
      graphic = *hID
      gType = TAG_NAMES(graphic, /STRUCTURE_NAME)
      case gType of
         'VIZ3D_ORTHO_PLANE': begin
            altState = sMainState
            altState.curData = graphic.curData
            altState.sSliceState.orthoDir = graphic.orthoDir
            altState.sSliceState.orthoPos = graphic.orthoPos
            altState.sSliceState.drawMode = graphic.drawMode

            lowThresh = *(altState.sThreshState.hLowThresh)
            lowThresh[sMainState.curData] = graphic.lowThresh
            hLowThresh = PTR_NEW(lowThresh, /NO_COPY)
            altState.sThreshState.hLowThresh = hLowThresh

            highThresh = *(altState.sThreshState.hHighThresh)
            highThresh[sMainState.curData] = graphic.highThresh
            hHighThresh = PTR_NEW(highThresh, /NO_COPY)
            altState.sThreshState.hHighThresh = hHighThresh

            transVal = *(altState.sThreshState.hTransVal)
            transVal[sMainState.curData] = graphic.transVal
            hTransVal = PTR_NEW(transVal, /NO_COPY)
            altState.sThreshState.hTransVal = hTransVal

            Viz3D_OrthoPlaneDraw, TEMPORARY(altState), /SKIP_ADD, $
               /SKIP_DRAW

            PTR_FREE, hLowThresh
            PTR_FREE, hHighThresh
            PTR_FREE, hTransVal
         end
         'VIZ3D_OBLIQ_PLANE': begin
            altState = sMainState
            altState.curData = graphic.curData
            altState.sSliceState.drawMode = graphic.drawMode
            altState.sSliceState.obliqNormal = graphic.obliqNormal
            altState.sSliceState.obliqCenter = graphic.obliqCenter

            lowThresh = *(altState.sThreshState.hLowThresh)
            lowThresh[sMainState.curData] = graphic.lowThresh
            hLowThresh = PTR_NEW(lowThresh, /NO_COPY)
            altState.sThreshState.hLowThresh = hLowThresh

            highThresh = *(altState.sThreshState.hHighThresh)
            highThresh[sMainState.curData] = graphic.highThresh
            hHighThresh = PTR_NEW(highThresh, /NO_COPY)
            altState.sThreshState.hHighThresh = hHighThresh

            transVal = *(altState.sThreshState.hTransVal)
            transVal[sMainState.curData] = graphic.transVal
            hTransVal = PTR_NEW(transVal, /NO_COPY)
            altState.sThreshState.hTransVal = hTransVal

            vertPlane = *(graphic.hVertPlane)
            vert3D = *(graphic.hVert3D)
            Viz3D_ObliqPlaneDraw, TEMPORARY(altState), vertPlane, vert3D, $
               /SKIP_ADD, /SKIP_DRAW

            PTR_FREE, hLowThresh
            PTR_FREE, hHighThresh
            PTR_FREE, hTransVal
         end
         'VIZ3D_BLOCK': begin
            altState = sMainState
            altState.curData = graphic.curData
            altState.sBlockState.c1 = graphic.c1
            altState.sBlockState.c2 = graphic.c2
            altState.sBlockState.blockMode = graphic.blockMode

            lowThresh = *(altState.sThreshState.hLowThresh)
            lowThresh[sMainState.curData] = graphic.lowThresh
            hLowThresh = PTR_NEW(lowThresh, /NO_COPY)
            altState.sThreshState.hLowThresh = hLowThresh

            highThresh = *(altState.sThreshState.hHighThresh)
            highThresh[sMainState.curData] = graphic.highThresh
            hHighThresh = PTR_NEW(highThresh, /NO_COPY)
            altState.sThreshState.hHighThresh = hHighThresh

            transVal = *(altState.sThreshState.hTransVal)
            transVal[sMainState.curData] = graphic.transVal
            hTransVal = PTR_NEW(transVal, /NO_COPY)
            altState.sThreshState.hTransVal = hTransVal

            Viz3D_BlockDraw, TEMPORARY(altState), /SKIP_ADD, $
               /SKIP_DRAW

            PTR_FREE, hLowThresh
            PTR_FREE, hHighThresh
            PTR_FREE, hTransVal
         end
         'VIZ3D_SURF': begin
            altState = sMainState
            altState.curData = graphic.curData
            altState.sSurfState.surfSide = graphic.surfSide

            surfThresh = *(altState.sSurfState.hSurfThresh)
            surfThresh[sMainState.curData] = graphic.surfThresh
            hSurfThresh = PTR_NEW(surfThresh, /NO_COPY)
            altState.sSurfState.hSurfThresh = hSurfThresh

            Viz3D_SurfDraw, TEMPORARY(altState), graphic.hVertList, $
               graphic.hPolyList, graphic.hColorList, /SKIP_ADD, /SKIP_DRAW

            PTR_FREE, hSurfThresh
         end
         'VIZ3D_PROJ': begin
            altState = sMainState
            altState.curData = graphic.curData
            altState.sProjState.projReso = graphic.projReso
            altState.sProjState.projType = graphic.projType
            altState.sProjState.depthQ = graphic.depthQ

            lowThresh = *(altState.sThreshState.hLowThresh)
            lowThresh[sMainState.curData] = graphic.lowThresh
            hLowThresh = PTR_NEW(lowThresh, /NO_COPY)
            altState.sThreshState.hLowThresh = hLowThresh

            highThresh = *(altState.sThreshState.hHighThresh)
            highThresh[sMainState.curData] = graphic.highThresh
            hHighThresh = PTR_NEW(highThresh, /NO_COPY)
            altState.sThreshState.hHighThresh = hHighThresh

            transVal = *(altState.sThreshState.hTransVal)
            transVal[sMainState.curData] = graphic.transVal
            hTransVal = PTR_NEW(transVal, /NO_COPY)
            altState.sThreshState.hTransVal = hTransVal

            Viz3D_ProjDraw, TEMPORARY(altState), /SKIP_ADD, /SKIP_DRAW

            PTR_FREE, hLowThresh
            PTR_FREE, hHighThresh
            PTR_FREE, hTransVal
         end
      endcase
      *hID = graphic
      displayListIndex = displayListIndex + 1
      if (displayListIndex LT N_ELEMENTS(*(sMainState.hDisplayList))) then $
         hID = (*(sMainState.hDisplayList))[displayListIndex] $
      else hID = PTR_NEW()      ; null since end of list reached
   endwhile

   Viz3D_DrawData, sMainState

end
;******************************************************************************


;******************************************************************************
; Return an xyz data coordinate on the face of the cube.
; This procedure uses the buffers saved in sMainState.frontDepth,
; sMainState.backDepth, sMainState.frontImage, and sMainState.backImage
; (the Viz3D_FillDepth procedure computes them).
; The ON_FACE keyword returns the face that (dX, dY) maps to :
;   0- not on any face,  1- XY,   2- XZ,   3- YZ.
; (dX, dY) is the 2D screen coordinate (where the user clicked).
; invTrans is the inverse of the current view transformation
; (from sMainState.sViewState.invTrans).
function Viz3D_XYZPos, dX, dY, hDepth, hImage, invTrans, ON_FACE=onFace

   COMPILE_OPT hidden, idl2

   szDepth = SIZE(*hDepth)
   dX = (dX > 0) < (szDepth[1]-1)
   dY = (dY > 0) < (szDepth[2]-1)
   dZ = (*hDepth)[dX, dY]
   onFace = (*hImage)[dX, dY]


   zMax = (2L^15L) - 3L
   zMin = (-zmax)

   if (dZ le (zMin)) then return, [0.0, 0.0, 0.0] ; Not on any face.

   nX = Float(dX) / Float(!D.X_Size - 1)
   nY = Float(dY) / Float(!D.Y_Size - 1)
   nZ = (Float(dZ) - Float(zMin)) / Float(zMax - zMin)

   xyzNorm = [nX, nY, nZ, 1.0] # invTrans
   xyzNorm = xyzNorm / xyzNorm[3]

   xPos = (xyzNorm[0] - !X.S[0]) / !X.S[1]
   yPos = (xyzNorm[1] - !Y.S[0]) / !Y.S[1]
   zPos = (xyzNorm[2] - !Z.S[0]) / !Z.S[1]

   ; Return the 3D data coordinate on a face of the cube.
   return, [xPos, yPos, zPos]

end
;******************************************************************************


;******************************************************************************
; Event handler for slice events.
pro Viz3D_SliceEvent, event

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal

   if (uVal eq 'wMainDraw') then begin
      wDrawBase = WIDGET_INFO(event.id, /PARENT)
      WIDGET_CONTROL, wDrawBase, GET_UVALUE=wMainBase
   endif else begin
      wMainBase = event.top
      WIDGET_CONTROL, /HOURGLASS
   endelse

   WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY

   case uVal of
      'wMainDraw': begin ; Big view window.
         if (event.release ge 1) then begin
            WIDGET_CONTROL, sMainState.wMainDraw, DRAW_MOTION_EVENTS=0, $
               /DRAW_BUTTON_EVENTS
            WIDGET_CONTROL, /HOURGLASS
         endif

         if (event.release gt 1) then begin ; Toggle plane direction.
            if (sMainState.sSliceState.planeMode eq 1) then begin
               case sMainState.sSliceState.orthoDir of
                  1: begin ; X, toggle to Y.
                     sMainState.sSliceState.orthoDir = 2
                     WIDGET_CONTROL,sMainState.sSliceState.wSliceYBttn, $
                        /SET_BUTTON
                  end
                  2: begin ; Y, toggle to Z.
                     sMainState.sSliceState.orthoDir = 3
                     WIDGET_CONTROL,sMainState.sSliceState.wSliceZBttn, $
                        /SET_BUTTON
                  end
                  3: begin ; Z, toggle to X.
                     sMainState.sSliceState.orthoDir = 1
                     WIDGET_CONTROL,sMainState.sSliceState.wSliceXBttn, $
                        /SET_BUTTON
                  end
               endcase
               Viz3D_SliceShow, sMainState
            endif
         endif

         if ((event.press eq 1) or $
            ((event.press eq 0) and (event.release eq 0))) then begin

            if (event.press eq 1) then begin
               ; Initiate dynamic plane positioning.
               WIDGET_CONTROL, sMainState.wMainDraw, /DRAW_MOTION_EVENTS, $
                  /DRAW_BUTTON_EVENTS
               sMainState.cleanupView = 1
            endif

            ; Compute a 3D cube surface location from the current
            ; cursor position.
            xyzPos = Viz3D_XYZPos(event.x, event.y, sMainState.hFrontDepth, $
               sMainState.hFrontImage, sMainState.sViewState.invTrans, $
               ON_FACE=onFace)
            ; Normalize the 3D coordinate.
            xyzPos = Temporary(xyzPos) / [sMainState.sViewState.xMax, $
               sMainState.sViewState.yMax, sMainState.sViewState.zMax]

            DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, 0, 0, $
                          sMainState.pixWin]

            if (onFace gt 0) then begin
               ; Cursor position is currently somewhere on the cube.

               if (sMainState.sSliceState.planeMode eq 1) then begin
                  ; Drag an orthogonal plane.

                  case sMainState.sSliceState.orthoDir of
                     1: begin ; X
                        Plots, xyzPos[0], [0,1,1,0,0], [0,0,1,1,0], /NORMAL, $
                           /T3D, COLOR=Viz3D_TransColor(sMainState, $
                           sMainState.sSliceState.edgeColor)

                        WIDGET_CONTROL, sMainState.wStatText, $
                           SET_VALUE=('X: ' + $
                           STRING(xyzPos[0]*sMainState.sViewState.xMax))
                     end
                     2: begin ; Y
                        Plots, [0,1,1,0,0], xyzPos[1], [0,0,1,1,0], /NORMAL, $
                           /T3D, COLOR=Viz3D_TransColor(sMainState, $
                           sMainState.sSliceState.edgeColor)

                        WIDGET_CONTROL, sMainState.wStatText, $
                           SET_VALUE=('Y: ' + $
                           STRING(xyzPos[1]*sMainState.sViewState.yMax))
                     end
                     3: begin ; Z
                        Plots, [0,1,1,0,0], [0,0,1,1,0], xyzPos[2], /NORMAL, $
                           /T3D, COLOR=Viz3D_TransColor(sMainState, $
                           sMainState.sSliceState.edgeColor)

                        WIDGET_CONTROL, sMainState.wStatText, $
                           SET_VALUE=('Z: ' + $
                           STRING(xyzPos[2]*sMainState.sViewState.zMax))
                     end
                  endcase
                  EMPTY

               endif else begin ; Drag an oblique plane.

                  edgeColor = Viz3D_TransColor(sMainState, $
                                 sMainState.sSliceState.edgeColor)
                  normColor = Viz3D_TransColor(sMainState, $
                                 sMainState.sSliceState.normColor)

                  ; Move plane normal.

                  if (sMainState.sSliceState.obliqMove eq 0) then begin
                     sMainState.sSliceState.obliqNormal = $
                        xyzPos - sMainState.sSliceState.obliqCenter
                     maxDim = sMainState.sViewState.xMax > $
                              sMainState.sViewState.yMax > $
                              sMainState.sViewState.zMax
                     sMainState.sSliceState.obliqNormal[0] = $
                        sMainState.sSliceState.obliqNormal[0] * $
                        sMainState.sViewState.xMax / maxDim
                     sMainState.sSliceState.obliqNormal[1] = $
                        sMainState.sSliceState.obliqNormal[1] * $
                        sMainState.sViewState.yMax / maxDim
                     sMainState.sSliceState.obliqNormal[2] = $
                        sMainState.sSliceState.obliqNormal[2] * $
                        sMainState.sViewState.zMax / maxDim
                     Viz3D_DrawSliceOblique, $
                        sMainState.sSliceState.obliqCenter, $
                        sMainState.sSliceState.obliqNormal, 0, edgeColor, $
                        sMainState.sViewState.xMax, $
                        sMainState.sViewState.yMax, $
                        sMainState.sViewState.zMax, $
                        sMainState.sViewState.zScale, $
                        NORMCOLOR=normColor, /SKIP_FILL
                     sMainState.sSliceState.obliqNormal = $
                        sMainState.sSliceState.obliqNormal / $
                        SQRT(TOTAL(sMainState.sSliceState.obliqNormal^2))
                     WIDGET_CONTROL, sMainState.wStatText, $
                        SET_VALUE=('Normal: ' + $
                        STRING(sMainState.sSliceState.obliqNormal, $
                           FORMAT='(F6.3,1X,F6.3,1X,F6.3)'))

                  endif else begin ; Move plane center.

                     case onFace of
                        1: begin
                           sMainState.sSliceState.obliqCenter[[0,1]] = $
                              xyzPos[[0,1]]
                        end
                        2: begin
                           sMainState.sSliceState.obliqCenter[[0,2]] = $
                              xyzPos[[0,2]]
                        end
                        3: begin
                           sMainState.sSliceState.obliqCenter[[1,2]] = $
                              xyzPos[[1,2]]
                        end
                     endcase
                     Viz3D_DrawSliceOblique, $
                        sMainState.sSliceState.obliqCenter, $
                        sMainState.sSliceState.obliqNormal, 0, edgeColor, $
                        sMainState.sViewState.xMax, $
                        sMainState.sViewState.yMax, $
                        sMainState.sViewState.zMax, $
                        sMainState.sViewState.zScale, $
                        NORMCOLOR=normColor, /SKIP_FILL
                     xyzPos = sMainState.sSliceState.obliqCenter * $
                              [sMainState.sViewState.xMax, $
                               sMainState.sViewState.yMax, $
                               sMainState.sViewState.zMax]
                     WIDGET_CONTROL, sMainState.wStatText, $
                        SET_VALUE=('Center: ' + $
                        STRING(xyzPos, FORMAT='(F6.3,1X,F6.3,1X,F6.3)'))
                  endelse
               endelse
            endif
         endif

         if (event.release eq 1) then begin ; Cleanup and draw slice.
            xyzPos = Viz3D_XYZPos(event.x, event.y, sMainState.hFrontDepth, $
               sMainState.hFrontImage, sMainState.sViewState.invTrans, $
               ON_FACE=onFace)
            xyzPos = Temporary(xyzPos) / [sMainState.sViewState.xMax, $
               sMainState.sViewState.yMax, sMainState.sViewState.zMax]

            if (TOTAL(ABS(xyzPos)) gt 0.0) then begin
               ; Plane is now positioned.

               if (sMainState.sSliceState.planeMode eq 1) then begin
                  ; Orthogonal plane.
                  case sMainState.sSliceState.orthoDir of
                     1: sMainState.sSliceState.orthoPos = xyzPos[0]
                     2: sMainState.sSliceState.orthoPos = xyzPos[1]
                     3: sMainState.sSliceState.orthoPos = xyzPos[2]
                  endcase
                  ; Update the small slice status window.
                  Viz3D_SliceShow, sMainState

                  ; Draw orthogonal slice with data in the big window.
                  Viz3D_OrthoPlaneDraw, sMainState
               endif else begin ; Oblique plane.
                  ; Normal moved.
                  if (sMainState.sSliceState.obliqMove eq 0) then begin
                     sMainState.sSliceState.obliqNormal = $
                        xyzPos - sMainState.sSliceState.obliqCenter
                     maxDim = sMainState.sViewState.xMax > $
                              sMainState.sViewState.yMax > $
                              sMainState.sViewState.zMax
                     sMainState.sSliceState.obliqNormal[0] = $
                        sMainState.sSliceState.obliqNormal[0] * $
                        sMainState.sViewState.xMax / maxDim
                     sMainState.sSliceState.obliqNormal[1] = $
                        sMainState.sSliceState.obliqNormal[1] * $
                        sMainState.sViewState.yMax / maxDim
                     sMainState.sSliceState.obliqNormal[2] = $
                        sMainState.sSliceState.obliqNormal[2] * $
                        sMainState.sViewState.zMax / maxDim
                  endif else begin ; Center moved.
                     case onFace of
                        1: begin
                           sMainState.sSliceState.obliqCenter[[0,1]] = $
                              xyzPos[[0,1]]
                        end
                        2: begin
                           sMainState.sSliceState.obliqCenter[[0,2]] = $
                              xyzPos[[0,2]]
                        end
                        3: begin
                           sMainState.sSliceState.obliqCenter[[1,2]] = $
                              xyzPos[[1,2]]
                        end
                     endcase
                  endelse
                  fillColor = Viz3D_TransColor(sMainState, $
                                 sMainState.sSliceState.fillColor)
                  edgeColor = Viz3D_TransColor(sMainState, $
                                 sMainState.sSliceState.edgeColor)
                  normColor = Viz3D_TransColor(sMainState, $
                                 sMainState.sSliceState.normColor)
                  backColor = Viz3D_TransColor(sMainState, $
                                 sMainState.backColor)

                  ; Update the small slice status window.
                  Viz3D_SliceShow, sMainState
               endelse
            endif
         endif
      end
      'wSliceDraw': begin ; Event in small slice plane window.
         if (event.release gt 0) then begin
            ; Toggle the orthogonal plane direction.

            if (sMainState.sSliceState.planeMode eq 1) then begin
               case sMainState.sSliceState.orthoDir of
                  1: begin
                     sMainState.sSliceState.orthoDir = 2
                     WIDGET_CONTROL,sMainState.sSliceState.wSliceYBttn, $
                        /SET_BUTTON
                  end
                  2: begin
                     sMainState.sSliceState.orthoDir = 3
                     WIDGET_CONTROL,sMainState.sSliceState.wSliceZBttn, $
                        /SET_BUTTON
                  end
                  3: begin
                     sMainState.sSliceState.orthoDir = 1
                     WIDGET_CONTROL,sMainState.sSliceState.wSliceXBttn, $
                        /SET_BUTTON
                  end
               endcase
            endif else begin ; Reset the oblique slicing plane position.
               sMainState.sSliceState.obliqCenter = [0.5, 0.5, 0.5]
               sMainState.sSliceState.obliqNormal = [0.0, 0.0, 1.0]
            endelse

            ; Update the small slice status window.
            Viz3D_SliceShow, sMainState
            DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, $
                          0, 0, sMainState.pixWin]

            WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
         endif
      end
      'wSliceOrthoBttn': begin ; Set orthogonal slice mode.
         sMainState.sSliceState.planeMode = 1
         WIDGET_CONTROL, sMainState.sSliceState.wSliceObliqBase, MAP=0
         WIDGET_CONTROL, sMainState.sSliceState.wSliceOrthoBase, MAP=1
         Viz3D_SliceShow, sMainState
         DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, $
                       0, 0, sMainState.pixWin]
         WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
      end
      'wSliceObliqBttn': begin ; Set oblique slice mode.
         sMainState.sSliceState.planeMode = 0
         WIDGET_CONTROL, sMainState.sSliceState.wSliceOrthoBase, MAP=0
         WIDGET_CONTROL, sMainState.sSliceState.wSliceObliqBase, MAP=1
         fillColor = Viz3D_TransColor(sMainState, $
                        sMainState.sSliceState.fillColor)
         edgeColor = Viz3D_TransColor(sMainState, $
                        sMainState.sSliceState.edgeColor)
         normColor = Viz3D_TransColor(sMainState, $
                        sMainState.sSliceState.normColor)
         backColor = Viz3D_TransColor(sMainState, $
                        sMainState.backColor)
         Viz3D_SliceShow, sMainState
      end
      'wSliceDrawBttn': begin ; Set "Draw" mode for slices.
         sMainState.sSliceState.drawMode = 0
      end
      'wSliceExpoBttn': begin ; Set "Expose" mode for slices.
         sMainState.sSliceState.drawMode = 1
      end
      'wSliceXBttn': begin ; Set orthogonal slice direction X.
         sMainState.sSliceState.orthoDir = 1
         Viz3D_SliceShow, sMainState
      end
      'wSliceYBttn': begin ; Set orthogonal slice direction Y.
         sMainState.sSliceState.orthoDir = 2
         Viz3D_SliceShow, sMainState
      end
      'wSliceZBttn': begin ; Set orthogonal slice direction Z.
         sMainState.sSliceState.orthoDir = 3
         Viz3D_SliceShow, sMainState
      end
      'wSliceNormalBttn': begin ; Set oblique slice mode for normal movement.
         sMainState.sSliceState.obliqMove = 0
      end
      'wSliceCenterBttn': begin ; Set oblique slice mode for center movement.
         sMainState.sSliceState.obliqMove = 1
      end
      'wSliceObliqGoBttn': begin ; Draw the slice.
         fillColor = Viz3D_TransColor(sMainState, $
                        sMainState.sSliceState.fillColor)
         edgeColor = Viz3D_TransColor(sMainState, $
                        sMainState.sSliceState.edgeColor)
         Viz3D_DrawSliceOblique, sMainState.sSliceState.obliqCenter, $
            sMainState.sSliceState.obliqNormal, $
            fillColor, edgeColor, $
            sMainState.sViewState.xMax, $
            sMainState.sViewState.yMax, $
            sMainState.sViewState.zMax, $
            sMainState.sViewState.zScale, /SKIP_FILL, $
            VERT_PLANE=vertPlane, VERT_3D=vert3D
         ; Draw oblique slice with data in the big window.
         Viz3D_ObliqPlaneDraw, sMainState, vertPlane, vert3D
      end
      else:
   endcase

   WIDGET_CONTROL, wMainBase, SET_UVALUE=sMainState, /NO_COPY

end
;******************************************************************************


;******************************************************************************
; Event handler for block events.
pro Viz3D_BlockEvent, event

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal
   if (uVal eq 'wMainDraw') then begin
      wDrawBase = WIDGET_INFO(event.id, /PARENT)
      WIDGET_CONTROL, wDrawBase, GET_UVALUE=wMainBase
   endif else begin
      wMainBase = event.top
      WIDGET_CONTROL, /HOURGLASS
   endelse

   WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY

   case uVal of
      'wMainDraw': begin ; Big draw window, move block.
         if (event.release ge 1) then begin
            WIDGET_CONTROL, sMainState.wMainDraw, DRAW_MOTION_EVENTS=0, $
               /DRAW_BUTTON_EVENTS
            WIDGET_CONTROL, /HOURGLASS
         endif

         if ((event.press ge 1) or $
            ((event.press eq 0) and (event.release eq 0))) then begin

            if (event.press ge 1) then begin
               ; Initiate dynamic block positioning.
               WIDGET_CONTROL, sMainState.wMainDraw, /DRAW_MOTION_EVENTS, $
                  /DRAW_BUTTON_EVENTS
               sMainState.cleanupView = 1
            endif
            if (event.press eq 1) then $
               sMainState.sBlockState.frontBack = 1
            if (event.press gt 1) then $
               sMainState.sBlockState.frontBack = 0

            ; Compute a 3D cube surface location from the current
            ; cursor position.
            xyzPos = Viz3D_XYZPos(event.x, event.y, sMainState.hFrontDepth, $
               sMainState.hFrontImage, sMainState.sViewState.invTrans, $
               ON_FACE=onFace)

            xyzPos = ROUND(xyzPos)

            if (onFace gt 0) then begin ; On cube, draw block.
               case (onFace) of
                  1: begin
                     if (sMainState.sBlockState.frontBack) then begin
                        sMainState.sBlockState.c1[0] = xyzPos[0]
                        sMainState.sBlockState.c1[1] = xyzPos[1]
                     endif else begin
                        sMainState.sBlockState.c2[0] = xyzPos[0]
                        sMainState.sBlockState.c2[1] = xyzPos[1]
                     endelse
                  end
                  2: begin
                     if (sMainState.sBlockState.frontBack) then begin
                        sMainState.sBlockState.c1[0] = xyzPos[0]
                        sMainState.sBlockState.c1[2] = xyzPos[2]
                     endif else begin
                        sMainState.sBlockState.c2[0] = xyzPos[0]
                        sMainState.sBlockState.c2[2] = xyzPos[2]
                     endelse
                  end
                  3: begin
                     if (sMainState.sBlockState.frontBack) then begin
                        sMainState.sBlockState.c1[1] = xyzPos[1]
                        sMainState.sBlockState.c1[2] = xyzPos[2]
                     endif else begin
                        sMainState.sBlockState.c2[1] = xyzPos[1]
                        sMainState.sBlockState.c2[2] = xyzPos[2]
                     endelse
                  end
               endcase
            endif
         endif

         DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, 0, 0, $
                       sMainState.pixWin]
         Viz3D_BlockShow, sMainState, /DIRECT

         if (event.release ge 1) then begin ; Cleanup.
            Viz3D_BlockShow, sMainState
         endif

         WIDGET_CONTROL, sMainState.wStatText, $
            SET_VALUE=('(' + $
            STRTRIM(STRING(sMainState.sBlockState.c1[0]),2) + ', ' + $
            STRTRIM(STRING(sMainState.sBlockState.c1[1]),2) + ', ' + $
            STRTRIM(STRING(sMainState.sBlockState.c1[2]),2) + '), (' + $
            STRTRIM(STRING(sMainState.sBlockState.c2[0]),2) + ', ' + $
            STRTRIM(STRING(sMainState.sBlockState.c2[1]),2) + ', ' + $
            STRTRIM(STRING(sMainState.sBlockState.c2[2]),2) + ')')

      end
      'wBlockDraw': ; Small block window, do nothing.
      'wBlockAddBttn': sMainState.sBlockState.blockMode = 1
      'wBlockSubBttn': sMainState.sBlockState.blockMode = 0
      'wBlockGoBttn': begin ; Draw it.
         Viz3D_BlockDraw, sMainState
      end
   endcase

   WIDGET_CONTROL, wMainBase, SET_UVALUE=sMainState, /NO_COPY
end
;******************************************************************************


;******************************************************************************
; Event handler for surface events.
pro Viz3D_SurfEvent, event

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal
   if (uVal ne 'wSurfDraw') then WIDGET_CONTROL, /HOURGLASS

   if (uVal eq 'wMainDraw') then begin
      wDrawBase = WIDGET_INFO(event.id, /PARENT)
      WIDGET_CONTROL, wDrawBase, GET_UVALUE=wMainBase
   endif else wMainBase = event.top

   WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY

   case uVal of
      'wMainDraw': ; Big draw window, do nothing.
      'wSurfDraw': begin
         if (event.release le 0) then begin
            ; Determine which to set, high or low.
            WSET, sMainState.sThreshState.threshWin
            norm = FLOAT(event.x) / FLOAT(!D.X_SIZE)
            norm = (norm - 0.03) / (0.97 - 0.03)
            lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
            hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
            newThresh = (norm * (hTh - lTh)) + lTh
            surfThresh = *(sMainState.sSurfState.hSurfThresh)
            surfThresh[sMainState.curData] = newThresh
            *(sMainState.sSurfState.hSurfThresh) = surfThresh
            WIDGET_CONTROL, event.id, /DRAW_MOTION_EVENTS, $
               /DRAW_BUTTON_EVENTS
            WSET, sMainState.mainWin
         endif else begin
            WIDGET_CONTROL, event.id, DRAW_MOTION_EVENTS=0, $
               /DRAW_BUTTON_EVENTS
            WIDGET_CONTROL, /HOURGLASS
         endelse

         Viz3D_SurfShow, sMainState

      end
      'wSurfText': begin
         WIDGET_CONTROL, event.id, GET_VALUE=newThresh
         lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
         hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
         newThresh = FLOAT(newThresh[0])
         newThresh = (newThresh > lTh) < hTh
         surfThresh = *(sMainState.sSurfState.hSurfThresh)
         surfThresh[sMainState.curData] = newThresh
         *(sMainState.sSurfState.hSurfThresh) = surfThresh
         WIDGET_CONTROL, event.id, SET_VALUE=STRTRIM(STRING(newThresh),2)
         Viz3D_SurfShow, sMainState
      end
      'wSurfLowBttn': sMainState.sSurfState.surfSide = 0
      'wSurfHighBttn': sMainState.sSurfState.surfSide = 1
      'wSurfShadeDrop': sMainState.sSurfState.curShade = event.index
      'wSurfGoBttn': Viz3D_SurfDraw, sMainState, /CALC_SURF
   endcase

   WIDGET_CONTROL, wMainBase, SET_UVALUE=sMainState, /NO_COPY
end
;******************************************************************************


;******************************************************************************
; Event handler for projection events.
pro Viz3D_ProjectEvent, event
   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, /HOURGLASS

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal
   if (uVal eq 'wMainDraw') then begin
      wDrawBase = WIDGET_INFO(event.id, /PARENT)
      WIDGET_CONTROL, wDrawBase, GET_UVALUE=wMainBase
   endif else wMainBase = event.top

   WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY

   case uVal of
      'wMainDraw': ; Big draw window, do nothing.
      'wProjMaxBttn': sMainState.sProjState.projType = 0
      'wProjAvgBttn': sMainState.sProjState.projType = 1
      'wProjLowBttn': sMainState.sProjState.projReso = 0
      'wProjMedBttn': sMainState.sProjState.projReso = 1
      'wProjHighBttn': sMainState.sProjState.projReso = 2
      'wProjDQSlid': sMainState.sProjState.depthQ = event.value
      'wProjGoBttn': Viz3D_ProjDraw, sMainState
   endcase

   WIDGET_CONTROL, wMainBase, SET_UVALUE=sMainState, /NO_COPY
end
;******************************************************************************


;******************************************************************************
; Event handler for threshold events.
pro Viz3D_ThreshEvent, event

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal
   if (uVal ne 'wThreshDraw') then WIDGET_CONTROL, /HOURGLASS

   if (uVal eq 'wMainDraw') then begin
      wDrawBase = WIDGET_INFO(event.id, /PARENT)
      WIDGET_CONTROL, wDrawBase, GET_UVALUE=wMainBase
   endif else wMainBase = event.top

   WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY

   lTh = (*(sMainState.sThreshState.hLowThresh))[sMainState.curData]
   hTh = (*(sMainState.sThreshState.hHighThresh))[sMainState.curData]
   vTp = (*(sMainState.sThreshState.hTransVal))[sMainState.curData]

   minD = (*(sMainState.sThreshState.hMinData))[sMainState.curData]
   maxD = (*(sMainState.sThreshState.hMaxData))[sMainState.curData]

   case uVal of
      'wMainDraw': ; Big draw window, do nothing.
      'wThreshDraw': begin ; Set new threshold value via draw widget.
         if (event.release le 0) then begin
            if (event.press eq 1) then sMainState.sThreshState.moveType = 0
            if (event.press ge 2) then sMainState.sThreshState.moveType = 1
            if (sMainState.sThreshState.moveType eq 0) then begin
               ; Move high/low threshold
               ; Determine which to set, high or low.
               WSET, sMainState.sThreshState.threshWin
               norm = FLOAT(event.x) / FLOAT(!D.X_SIZE)
               norm = (norm - 0.03) / (0.97 - 0.03)
               newThresh = (norm * (maxD - minD)) + minD
               newThresh = (newThresh > minD) < maxD

               ; Closer to min threshold.
               if (ABS(newThresh - lTh) lt ABS(newThresh - hTh)) then begin
                  newThresh = newThresh < hTh
                  lowThresh = *(sMainState.sThreshState.hLowThresh)
                  lowThresh[sMainState.curData] = newThresh
                  *(sMainState.sThreshState.hLowThresh) = lowThresh
               endif
               ; Closer to max threshold.
               if (ABS(newThresh - lTh) gt ABS(newThresh - hTh)) then begin
                  newThresh = newThresh > lTh
                  highThresh = *(sMainState.sThreshState.hHighThresh)
                  highThresh[sMainState.curData] = newThresh
                  *(sMainState.sThreshState.hHighThresh) = highThresh
               endif
               if (ABS(newThresh - lTh) eq ABS(newThresh - hTh)) then begin
                  newThresh = (newThresh > minD) < maxD
                  if (newThresh gt hTh) then begin
                     highThresh = *(sMainState.sThreshState.hHighThresh)
                     highThresh[sMainState.curData] = newThresh
                     *(sMainState.sThreshState.hHighThresh) = highThresh
                  endif
                  if (newThresh lt lTh) then begin
                     lowThresh = *(sMainState.sThreshState.hLowThresh)
                     lowThresh[sMainState.curData] = newThresh
                     *(sMainState.sThreshState.hLowThresh) = lowThresh
                  endif
               endif

               ; Make sure the min and max threshold values are not equal.
               lowThresh = *(sMainState.sThreshState.hLowThresh)
               highThresh = *(sMainState.sThreshState.hHighThresh)
               if (lowThresh[sMainState.curData] eq $
                   highThresh[sMainState.curData]) then begin
                  lowThresh[sMainState.curData] = minD
                  highThresh[sMainState.curData] = maxD
               endif
               *(sMainState.sThreshState.hLowThresh) = lowThresh
               *(sMainState.sThreshState.hHighThresh) = highThresh

            endif else begin ; Move transparency.
               WSET, sMainState.sThreshState.threshWin
               norm = FLOAT(event.x) / FLOAT(!D.X_SIZE)
               norm = (norm - 0.03) / (0.97 - 0.03)
               newThresh = (norm * (maxD - minD)) + minD
               newThresh = (newThresh > minD) < maxD

               transVal = *(sMainState.sThreshState.hTransVal)
               transVal[sMainState.curData] = newThresh
               *(sMainState.sThreshState.hTransVal) = transVal
            endelse

            WIDGET_CONTROL, event.id, /DRAW_MOTION_EVENTS, $
               /DRAW_BUTTON_EVENTS
            WSET, sMainState.mainWin
         endif else begin
            WIDGET_CONTROL, event.id, DRAW_MOTION_EVENTS=0, $
               /DRAW_BUTTON_EVENTS
            WIDGET_CONTROL, /HOURGLASS
            if (event.release eq 1) then begin
               WIDGET_CONTROL, /HOURGLASS
               Viz3D_SurfShow, sMainState, /CALC_HIST
            endif
         endelse

         Viz3D_ThreshShow, sMainState, /DYNAMIC
      end
      'wThreshLowText': begin
         WIDGET_CONTROL, event.id, GET_VALUE=newThresh
         newThresh = FLOAT(newThresh[0])
         newThresh = (newThresh > minD) < hTh

         lowThresh = *(sMainState.sThreshState.hLowThresh)
         highThresh = *(sMainState.sThreshState.hHighThresh)
         lowThresh[sMainState.curData] = newThresh
         if (lowThresh[sMainState.curData] eq $
             highThresh[sMainState.curData]) then begin
            lowThresh[sMainState.curData] = minD
            highThresh[sMainState.curData] = maxD
         endif
         *(sMainState.sThreshState.hLowThresh) = lowThresh
         *(sMainState.sThreshState.hHighThresh) = highThresh

         WIDGET_CONTROL, event.id, SET_VALUE=STRTRIM(STRING(newThresh),2)
         Viz3D_ThreshShow, sMainState, /DYNAMIC
         Viz3D_SurfShow, sMainState, /CALC_HIST
      end
      'wThreshHighText': begin
         WIDGET_CONTROL, event.id, GET_VALUE=newThresh
         newThresh = FLOAT(newThresh[0])
         newThresh = (newThresh > lTh) < maxD

         lowThresh = *(sMainState.sThreshState.hLowThresh)
         highThresh = *(sMainState.sThreshState.hHighThresh)
         highThresh[sMainState.curData] = newThresh
         if (lowThresh[sMainState.curData] eq $
             highThresh[sMainState.curData]) then begin
            lowThresh[sMainState.curData] = minD
            highThresh[sMainState.curData] = maxD
         endif
         *(sMainState.sThreshState.hHighThresh) = highThresh
         *(sMainState.sThreshState.hLowThresh) = lowThresh

         WIDGET_CONTROL, event.id, SET_VALUE=STRTRIM(STRING(newThresh),2)
         Viz3D_ThreshShow, sMainState, /DYNAMIC
         Viz3D_SurfShow, sMainState, /CALC_HIST
      end
      'wThreshTransText': begin
         WIDGET_CONTROL, event.id, GET_VALUE=newThresh
         newThresh = FLOAT(newThresh[0])
         newThresh = (newThresh > minD) < maxD

         transVal = *(sMainState.sThreshState.hTransVal)
         transVal[sMainState.curData] = newThresh
         *(sMainState.sThreshState.hTransVal) = transVal

         WIDGET_CONTROL, event.id, SET_VALUE=STRTRIM(STRING(newThresh),2)
         Viz3D_ThreshShow, sMainState, /DYNAMIC
      end
   endcase

   WIDGET_CONTROL, wMainBase, SET_UVALUE=sMainState, /NO_COPY
end
;******************************************************************************


;******************************************************************************
; Event handler for profile events.
pro Viz3D_ProfileEvent, event

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal
   if (uVal eq 'wMainDraw') then begin
      wDrawBase = WIDGET_INFO(event.id, /PARENT)
      WIDGET_CONTROL, wDrawBase, GET_UVALUE=wMainBase
   endif else begin
      wMainBase = event.top
      WIDGET_CONTROL, /HOURGLASS
   endelse

   WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY

   case uVal of
      'wProfDraw': ; Profile window, do nothing.
      'wMainDraw': begin ; Big draw window.
         if (event.release ge 1) then begin
            WIDGET_CONTROL, sMainState.wMainDraw, DRAW_MOTION_EVENTS=0, $
               /DRAW_BUTTON_EVENTS
            WIDGET_CONTROL, /HOURGLASS
         endif

         if ((event.press ge 1) or $
            ((event.press eq 0) and (event.release eq 0))) then begin

            if (event.press ge 1) then begin
               ; Initiate dynamic profile positioning.
               WIDGET_CONTROL, sMainState.wMainDraw, /DRAW_MOTION_EVENTS, $
                  /DRAW_BUTTON_EVENTS
               sMainState.cleanupView = 1
            endif
            if (event.press eq 1) then $
               sMainState.sProfState.frontBack = 1
            if (event.press gt 1) then $
               sMainState.sProfState.frontBack = 0

            ; Compute a 3D cube surface location from the current
            ; cursor position.
            if (sMainState.sProfState.frontBack) then $
               xyzPos = Viz3D_XYZPos(event.x, event.y, $
                  sMainState.hFrontDepth, sMainState.hFrontImage, $
                  sMainState.sViewState.invTrans, ON_FACE=onFace) $
            else $
               xyzPos = Viz3D_XYZPos(event.x, event.y, $
                  sMainState.hBackDepth, sMainState.hBackImage, $
                  sMainState.sViewState.invTrans, ON_FACE=onFace)

            xyzPos = ROUND(xyzPOS)

            if (onFace gt 0) then begin ; On cube, draw profile.
               if (sMainState.sProfState.profType eq 0) then begin
                  ; Orthogonal profile.
                  sMainState.sProfState.profDir = onFace
                  case onFace of
                     1: begin
                        sMainState.sProfState.x1Ortho = xyzPos[0]
                        sMainState.sProfState.x2Ortho = xyzPos[0]
                        sMainState.sProfState.y1Ortho = xyzPos[1]
                        sMainState.sProfState.y2Ortho = xyzPos[1]
                        sMainState.sProfState.z1Ortho = 0
                        sMainState.sProfState.z2Ortho = $
                           sMainState.szData[3] - 1
                     end
                     2: begin
                        sMainState.sProfState.x1Ortho = xyzPos[0]
                        sMainState.sProfState.x2Ortho = xyzPos[0]
                        sMainState.sProfState.y1Ortho = 0
                        sMainState.sProfState.y2Ortho = $
                           sMainState.szData[2] - 1
                        sMainState.sProfState.z1Ortho = xyzPos[2]
                        sMainState.sProfState.z2Ortho = xyzPos[2]
                     end
                     3: begin
                        sMainState.sProfState.x1Ortho = 0
                        sMainState.sProfState.x2Ortho = $
                           sMainState.szData[1] - 1
                        sMainState.sProfState.y1Ortho = xyzPos[1]
                        sMainState.sProfState.y2Ortho = xyzPos[1]
                        sMainState.sProfState.z1Ortho = xyzPos[2]
                        sMainState.sProfState.z2Ortho = xyzPos[2]
                     end
                  endcase
               endif else begin ; Oblique profile.
                  if (sMainState.sProfState.frontBack) then begin
                     sMainState.sProfState.x1Obliq = xyzPos[0]
                     sMainState.sProfState.y1Obliq = xyzPos[1]
                     sMainState.sProfState.z1Obliq = xyzPos[2]
                  endif else begin
                     sMainState.sProfState.x2Obliq = xyzPos[0]
                     sMainState.sProfState.y2Obliq = xyzPos[1]
                     sMainState.sProfState.z2Obliq = xyzPos[2]
                  endelse
               endelse

               if (onFace ne sMainState.sProfState.onFace) then begin
                  sMainState.sProfState.onFace = onFace
                  Viz3D_ProfShow, sMainState
               endif else Viz3D_ProfShow, sMainState, /DIRECT

               Viz3D_ProfDraw, sMainState

            endif
         endif
      end
      'wProfOrthoBttn': begin
         sMainState.sProfState.profType = 0
         Viz3D_ProfShow, sMainState
         Viz3D_ProfDraw, sMainState
      end
      'wProfObliqBttn': begin
         sMainState.sProfState.profType = 1
         Viz3D_ProfShow, sMainState
         Viz3D_ProfDraw, sMainState
      end
   endcase

   WIDGET_CONTROL, wMainBase, SET_UVALUE=sMainState, /NO_COPY
end
;******************************************************************************


;******************************************************************************
; Event handler for probe events.
pro Viz3D_ProbeEvent, event

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal
   if (uVal eq 'wMainDraw') then begin
      wDrawBase = WIDGET_INFO(event.id, /PARENT)
      WIDGET_CONTROL, wDrawBase, GET_UVALUE=wMainBase
   endif else begin
      wMainBase = event.top
      WIDGET_CONTROL, /HOURGLASS
   endelse

   WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY

   case uVal of
      'wMainDraw': begin ; Big draw window.
         if (event.release ge 1) then begin
            WIDGET_CONTROL, sMainState.wMainDraw, DRAW_MOTION_EVENTS=0, $
               /DRAW_BUTTON_EVENTS
            WIDGET_CONTROL, /HOURGLASS
         endif

         if (event.press ge 1) then $
            WIDGET_CONTROL, sMainState.wMainDraw, /DRAW_MOTION_EVENTS, $
               /DRAW_BUTTON_EVENTS

         if ((event.press ge 1) or $
            ((event.press eq 0) and (event.release eq 0))) then begin

            eX = (event.x > 0) < (!D.X_Size - 1)
            eY = (event.y > 0) < (!D.Y_Size - 1)
            nX = Float(eX) / Float(!D.X_Size - 1)
            nY = Float(eY) / Float(!D.Y_Size - 1)
            SET_PLOT, 'Z'
            nZ = (TVRD(eX, eY, 1, 1, CHANNEL=1, /WORDS))[0]
            SET_PLOT, sMainState.screenDevice

            zBack = ((*(sMainState.hBackDepth))[eX, eY])[0]

            zFront = ((*(sMainState.hfrontDepth))[eX, eY])[0]

            if (zFront gt (-32765)) then begin

               if (nZ gt zBack) then begin
                  nZ = (FLOAT(nZ) + 32765.0) / (2.0 * 32765.0)

                  xyzNorm = [nX, nY, nZ, 1.0] # sMainState.sViewState.invTrans
                  xyzNorm = xyzNorm / xyzNorm[3]

                  xPos = (((xyzNorm[0] - !X.S[0]) / !X.S[1]) > 0.0) < $
                     FLOAT(sMainState.szData[1]-1)
                  yPos = (((xyzNorm[1] - !Y.S[0]) / !Y.S[1]) > 0.0) < $
                     FLOAT(sMainState.szData[2]-1)
                  zPos = (((xyzNorm[2] - !Z.S[0]) / !Z.S[1]) > 0.0) < $
                     FLOAT(sMainState.szData[3]-1)

                  sMainState.sProbeState.x = xPos
                  sMainState.sProbeState.y = yPos
                  sMainState.sProbeState.z = zPos

                  Viz3D_ProbeDraw, sMainState

               endif else begin
                  WIDGET_CONTROL, sMainState.wStatText, $
                     SET_VALUE=('No data value')
                  WSET, sMainState.mainWin
                  DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, $
                                0, 0, sMainState.pixWin]
                  WIDGET_CONTROL, sMainState.sProbeState.wProbeXText, $
                     SET_VALUE=''
                  WIDGET_CONTROL, sMainState.sProbeState.wProbeYText, $
                     SET_VALUE=''
                  WIDGET_CONTROL, sMainState.sProbeState.wProbeZText, $
                     SET_VALUE=''
               endelse
            endif else begin
               WIDGET_CONTROL, sMainState.wStatText, $
                  SET_VALUE=('')
               WIDGET_CONTROL, sMainState.sProbeState.wProbeXText, $
                  SET_VALUE=''
               WIDGET_CONTROL, sMainState.sProbeState.wProbeYText, $
                  SET_VALUE=''
               WIDGET_CONTROL, sMainState.sProbeState.wProbeZText, $
                  SET_VALUE=''
               WSET, sMainState.mainWin
               DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, $
                             0, 0, sMainState.pixWin]
            endelse
         endif
      end
      'wProbeText': begin
         WIDGET_CONTROL, sMainState.sProbeState.wProbeXText, GET_VALUE=xT
         WIDGET_CONTROL, sMainState.sProbeState.wProbeYText, GET_VALUE=yT
         WIDGET_CONTROL, sMainState.sProbeState.wProbeZText, GET_VALUE=zT
         sMainState.sProbeState.x = (FLOAT(xt[0]) > 0.0) < $
            FLOAT(sMainState.szData[1]-1)
         sMainState.sProbeState.y = (FLOAT(yt[0]) > 0.0) < $
            FLOAT(sMainState.szData[2]-1)
         sMainState.sProbeState.z = (FLOAT(zt[0]) > 0.0) < $
            FLOAT(sMainState.szData[3]-1)
         xT = STRTRIM(STRING(sMainState.sProbeState.x), 2)
         yT = STRTRIM(STRING(sMainState.sProbeState.y), 2)
         zT = STRTRIM(STRING(sMainState.sProbeState.z), 2)
         WIDGET_CONTROL, sMainState.sProbeState.wProbeXText, SET_VALUE=xT
         WIDGET_CONTROL, sMainState.sProbeState.wProbeYText, SET_VALUE=yT
         WIDGET_CONTROL, sMainState.sProbeState.wProbeZText, SET_VALUE=zT
         Viz3D_ProbeDraw, sMainState
      end
   endcase

   WIDGET_CONTROL, wMainBase, SET_UVALUE=sMainState, /NO_COPY
end
;******************************************************************************


;******************************************************************************
; Event handler for view events.
pro Viz3D_ViewEvent, event
   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, /HOURGLASS

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal
   if (uVal eq 'wMainDraw') then begin
      wDrawBase = WIDGET_INFO(event.id, /PARENT)
      WIDGET_CONTROL, wDrawBase, GET_UVALUE=wMainBase
   endif else wMainBase = event.top

   WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY

   Viz3D_CleanView, sMainState, /SKIP_BUFFER

   redraw_flag = 0
   case uVal of
      'wMainDraw': ; Big draw window, do nothing.
      'wViewDraw': ; Small view window, do nothing.
      'wRot1Slid': begin
         if (sMainState.sViewState.ang1 ne event.value) then redraw_flag = 1
         sMainState.sViewState.ang1 = event.value
      end
      'wRot2Slid': begin
         if (sMainState.sViewState.ang2 ne event.value) then redraw_flag = 1
         sMainState.sViewState.ang2 = event.value
      end
      'wRot3Slid': begin
         if (sMainState.sViewState.ang3 ne event.value) then redraw_flag = 1
         sMainState.sViewState.ang3 = event.value
      end
      'wRot1Drop': begin
         case event.index of
            0: rot_axis = 'X'
            1: rot_axis = 'Y'
            2: rot_axis = 'Z'
         endcase
         if (sMainState.sViewState.dir1 ne rot_axis) then redraw_flag = 1
         sMainState.sViewState.dir1 = rot_axis
      end
      'wRot2Drop': begin
         case event.index of
            0: rot_axis = 'X'
            1: rot_axis = 'Y'
            2: rot_axis = 'Z'
         endcase
         if (sMainState.sViewState.dir2 ne rot_axis) then redraw_flag = 1
         sMainState.sViewState.dir2 = rot_axis
      end
      'wRot3Drop': begin
         case event.index of
            0: rot_axis = 'X'
            1: rot_axis = 'Y'
            2: rot_axis = 'Z'
         endcase
         if (sMainState.sViewState.dir3 ne rot_axis) then redraw_flag = 1
         sMainState.sViewState.dir3 = rot_axis
      end
      'wZoomSlid': begin
         zoomVal = FLOAT(event.value) / 100.0
         if (sMainState.sViewState.zoomFac ne zoomVal) then redraw_flag = 1
         sMainState.sViewState.zoomFac = zoomVal
      end
      'wZFacSlid': begin
         zFac = FLOAT(event.value) / 100.0
         if (sMainState.sViewState.zScale ne zFac) then redraw_flag = 1
         sMainState.sViewState.zScale = zFac
      end
      'wPerspSlid': begin
         perspDist = FLOAT(event.value)
         if (sMainState.sViewState.pDist ne perspDist) then redraw_flag = 1
         sMainState.sViewState.pDist = perspDist
      end
      'wViewDispBttn': begin
         sMainState.cleanupBuffer = 1
         Viz3D_CleanBuffer, sMainState
      end
   endcase

   if (redraw_flag) then begin
      sMainState.sViewState = $
         Viz3D_View(sMainState.sViewState.viewWin, $
            XMAX=sMainState.sViewState.xMax, $
            YMAX=sMainState.sViewState.yMax, $
            ZMAX=sMainState.sViewState.zMax, $
            ANG1=sMainState.sViewState.ang1, $
            ANG2=sMainState.sViewState.ang2, $
            ANG3=sMainState.sViewState.ang3, $
            DIR1=sMainState.sViewState.dir1, $
            DIR2=sMainState.sViewState.dir2, $
            DIR3=sMainState.sViewState.dir3, $
            ZOOM=sMainState.sViewState.zoomFac , $
            ZSCALE=sMainState.sViewState.zScale , $
            PERSP=sMainState.sViewState.pDist)
      Viz3D_ViewShow, sMainState
      sMainState.cleanupBuffer = 1
   endif

   WIDGET_CONTROL, wMainBase, SET_UVALUE=sMainState, /NO_COPY
end
;******************************************************************************


;******************************************************************************
; Differential shading event handler.
pro Viz3D_DiffShadeEvent, event
   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, /HOURGLASS

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal

   case uVal of
      'wDiffSlid': begin
         WIDGET_CONTROL, event.top, GET_UVALUE=leader
         WIDGET_CONTROL, leader, GET_UVALUE=sMainState, /NO_COPY
         sMainState.sColorState.diffShade = event.value
         sViz3DColors = sMainState.sColorState
         Viz3D_DiffColor, sViz3DColors
         sMainState.sColorState = TEMPORARY(sViz3DColors)

         if (event.drag eq 0) then begin
            WSET, sMainState.mainWin
            DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, 0, 0, $
                          sMainState.pixWin]
         endif

         WIDGET_CONTROL, leader, SET_UVALUE=sMainState, /NO_COPY
      end
      'wDiffCloseBttn': WIDGET_CONTROL, event.top, /DESTROY
   endcase
end
;******************************************************************************


;******************************************************************************
; Options event handler.
pro Viz3D_OptEvent, event
   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, /HOURGLASS

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal
   WIDGET_CONTROL, event.top, GET_UVALUE=hState
   sOptState = *hState

   WIDGET_CONTROL, sOptState.wOptSizeText, GET_VALUE=szText
   DEVICE, GET_SCREEN_SIZE=scrSize
   wSize = (FIX(szText[0]) > 256) < (scrSize[1] - 80)
   WIDGET_CONTROL, sOptState.wOptSizeText, SET_VALUE=STRING(wSize)
   sOptState.winXY = wSize

   case uVal of
      'wOptAxisOnBttn': sOptState.AxisOn = 1
      'wOptAxisOffBttn': sOptState.AxisOn = 0
      'wOptCubeOnBttn': sOptState.CubeOn = 1
      'wOptCubeOffBttn': sOptState.CubeOn = 0
      'wOptSizeText':
      'wOptOkBttn': begin
         sOptState.Ok = 1
         WIDGET_CONTROL, event.top, /DESTROY
      end
      'wOptCanBttn': begin
         sOptState.Ok = 0
         WIDGET_CONTROL, event.top, /DESTROY
      end
   endcase

   *hState = sOptState
end
;******************************************************************************


;******************************************************************************
; Main event handler.
pro Viz3D_Event, event

   COMPILE_OPT hidden, idl2

ON_IOERROR, IO_BAD

   WIDGET_CONTROL, /HOURGLASS

   WIDGET_CONTROL, event.id, GET_UVALUE=uVal
   if (uVal eq 'wMainDraw') then begin
      wDrawBase = WIDGET_INFO(event.id, /PARENT)
      WIDGET_CONTROL, wDrawBase, GET_UVALUE=mainID
   endif else mainID = event.top

   WIDGET_CONTROL, mainID, GET_UVALUE=sMainState, /NO_COPY

   if ((uVal ne 'wQuitBttn') AND (uVal ne 'wDelAllBttn')) then $
      Viz3D_CleanView, sMainState

   case uVal of
      'wQuitBttn': begin ; Exit.
         WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
         WIDGET_CONTROL, mainID, /Destroy
         return
      end

      'wLoadBttn': begin ; Load new data from a file.
         ; Get file name.
         dataFile = DIALOG_PICKFILE(GROUP=event.top, /READ, FILTER='*.dat', $
                             /MUST_EXIST)
         WIDGET_CONTROL, /HOURGLASS
         if (dataFile[0] eq '') then begin ; No file, user cancelled.
            WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
            return
         endif

         ; Open file.
         GET_LUN, lun
         OPENR, lun, dataFile

         ; Read in and check the data.

         goRead = 1
         count = 0

         ; Keep reading as long as there is valid data.
         while (goRead) do begin
            count = count + 1

            fileStat = FSTAT(lun)

            if (fileStat.cur_ptr eq fileStat.size) then begin ; At EOF.
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               CLOSE, lun
               FREE_LUN, lun
               return
            endif

            if ((fileStat.size - fileStat.cur_ptr) lt 29L) then begin
               ; Not enough bytes in file for data "header".
               ans = DIALOG_MESSAGE( $
                  'File does not contain valid 3-D data.', /ERROR)
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               CLOSE, lun
               FREE_LUN, lun
               return
            endif

            ; Read and check the number of dimensions.
            nDims = 0L
            READU, lun, nDims
            if ((ndims NE 3L) and (count eq 1)) then begin
               nDims = SWAP_ENDIAN(TEMPORARY(nDims))
               if (ndims NE 3L) then begin
                  ans = DIALOG_MESSAGE( $
                     'File does not contain valid 3-D data.', /ERROR)
                  WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
                  CLOSE, lun
                  FREE_LUN, lun
                  return
               endif
               swapData = 1
            endif else swapData = 0
            if ((ndims NE 3L) and (count gt 1)) then begin
               ans = DIALOG_MESSAGE( $
                  'File does not contain valid 3-D data.', /ERROR)
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               CLOSE, lun
               FREE_LUN, lun
               return
            endif

            ; Read the rest of the data "size" info.
            sizeDims = LONARR(3, /NOZERO)
            READU, lun, sizeDims
            dataType = 0L
            READU, lun, dataType
            nElems = 0L
            READU, lun, nElems

            ; Read the number of characters in data name.
            nNameChars = 0L
            READU, lun, nNameChars

            ; Byte swap if necessary.
            if (swapData) then begin
               sizeDims = SWAP_ENDIAN(TEMPORARY(sizeDims))
               dataType = SWAP_ENDIAN(TEMPORARY(dataType))
               nElems = SWAP_ENDIAN(TEMPORARY(nElems))
               nNameChars = SWAP_ENDIAN(TEMPORARY(nNameChars))
            endif

            ; Check the size of the data dimensions.
            if (MIN(sizeDims) LE 1L) then begin
               ans = DIALOG_MESSAGE( $
                  'File does not contain valid 3-D data.', /ERROR)
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               CLOSE, lun
               FREE_LUN, lun
               return
            endif

            ; Check the data type.
            if ((dataType LT 1L) OR (dataType GT 5)) then begin
               ans = DIALOG_MESSAGE( $
                  'File does not contain valid 3-D data.', /ERROR)
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               CLOSE, lun
               FREE_LUN, lun
               return
            endif

            ; Check the number of elements.
            if ((sizeDims[0]*sizeDims[1]*sizeDims[2]) NE nElems) then begin
               ans = DIALOG_MESSAGE( $
                  'File does not contain valid 3-D data.', /ERROR)
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               CLOSE, lun
               FREE_LUN, lun
               return
            endif

            ; Check the number of characters in data name.
            if (nNameChars LT 1L) then begin
               ans = DIALOG_MESSAGE( $
                  'File contains invalid 3-D data name.', /ERROR)
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               CLOSE, lun
               FREE_LUN, lun
               return
            endif

            ; See if there is enough bytes left in the file for data name.
            fileStat = FSTAT(lun)
            if ((fileStat.size - fileStat.cur_ptr) LT nNameChars) then begin
               ans = DIALOG_MESSAGE( $
                  'File does not contain valid 3-D data.', /ERROR)
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               CLOSE, lun
               FREE_LUN, lun
               return
            endif

            ; Read the data name.
            dataName = BYTARR(nNameChars, /NOZERO)
            READU, lun, dataName
            dataName = STRING(dataName)

            case dataType of
               1: nBytes = nElems
               2: nBytes = nElems * 2L
               3: nBytes = nElems * 4L
               4: nBytes = nElems * 4L
               5: nBytes = nElems * 8L
            endcase

            fileStat = FSTAT(lun)

            ; If there are enough bytes left in the file, then read the data.
            if ((fileStat.size - fileStat.cur_ptr) ge nBytes) then begin

               if (count eq 1) then begin
                  ; Make the first data active.
                  sMainState.curData = 0
                  sMainState.sSurfState.curShade = 0

                  ; Something valid to read, so clean out the existing data.
                  lData3D = *(sMainState.hDataList)
                  for i=0, N_ELEMENTS(lData3D)-1L do begin
                     if (PTR_VALID(lData3D[i])) then $
                        PTR_FREE, lData3D[i]
                  endfor
                  lDataHist = *(sMainState.sThreshState.hDataHist)
                  for i=0, N_ELEMENTS(lDataHist)-1L do begin
                     if (PTR_VALID(lDataHist[i])) then $
                        PTR_FREE, lDataHist[i]
                  endfor
                  lRangeHist = *(sMainState.sSurfState.hRangeHist)
                  for i=0, N_ELEMENTS(lRangeHist)-1L do begin
                     if (PTR_VALID(lRangeHist[i])) then $
                        PTR_FREE, lRangeHist[i]
                  endfor

                  sizeDims1 = sizeDims
               endif else begin
                  ; Check if Nth data is the same size as the first data.
                  index = WHERE(sizeDims1 ne sizeDims)
                  if (index[0] ge 0L) then begin
                     ans = DIALOG_MESSAGE( $
                        'Data dimensions are not consistent.', /ERROR)
                     WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
                     CLOSE, lun
                     FREE_LUN, lun
                     return
                  endif
               endelse

               ; Read the data.
               case dataType of
                  1: data3D = BYTARR(sizeDims[0], sizeDims[1], sizeDims[2], $
                                 /NOZERO)
                  2: data3D = INTARR(sizeDims[0], sizeDims[1], sizeDims[2], $
                                 /NOZERO)
                  3: data3D = LONARR(sizeDims[0], sizeDims[1], sizeDims[2], $
                                 /NOZERO)
                  4: data3D = FLTARR(sizeDims[0], sizeDims[1], sizeDims[2], $
                                 /NOZERO)
                  5: data3D = DBLARR(sizeDims[0], sizeDims[1], sizeDims[2], $
                                 /NOZERO)
               endcase
               READU, lun, data3D
               if (swapData) then data3D = SWAP_ENDIAN(TEMPORARY(data3D))

               if (count eq 1) then begin

                  ; First data, reset everything.

                  minData = MIN(data3D, MAX=maxData)
                  minData = DOUBLE(minData)
                  maxData = DOUBLE(maxData)
                  if (minData eq maxData) then maxData[0] = maxData[0] + 1.0D
                  bSize = (maxData - minData) / 200.0
                  n_samples = N_ELEMENTS(data3D)
                  IF (n_samples GT 32767L) THEN BEGIN
                     n_samples = 32767L + ROUND(SQRT(n_samples - 32767L))
                     s_index = RANDOMU(s, 1)
                     s_index = RANDOMU(s, n_samples)
                     s_index = LONG(TEMPORARY(s_index) * FLOAT(n_samples))
                     hist_data = FLOAT(data3D[TEMPORARY(s_index)])
                     dataHist = $
                        HISTOGRAM(TEMPORARY(hist_data), MIN=minData, MAX=maxData, $
                           BINSIZE=bSize) > 1.0
                  ENDIF ELSE BEGIN
                     dataHist = $
                        HISTOGRAM(FLOAT(data3D), MIN=minData, MAX=maxData, $
                           BINSIZE=bSize) > 1.0
                  ENDELSE
                  dataHist = ALOG(FLOAT(TEMPORARY(dataHist))) > 0.0

                  lDataHist = PTR_NEW(dataHist)
                  lRangeHist = PTR_NEW(dataHist, /NO_COPY)
                  *(sMainState.sThreshState.hDataHist) = lDataHist
                  *(sMainState.sSurfState.hRangeHist) = lRangeHist

                  surfThresh = (minData + maxData) / 2.0D
                  *(sMainState.sSurfState.hSurfThresh) = surfThresh

                  szData = SIZE(data3D)
                  lData3D = PTR_NEW(data3D, /NO_COPY)
                  *(sMainState.hDataList) = lData3D

                  WIDGET_CONTROL, sMainState.wDataModeBase, SENSITIVE=0
                  WIDGET_CONTROL, sMainState.sSurfState.wSurfShadeBase, $
                     SENSITIVE=0
                  WIDGET_CONTROL, sMainState.wDataDrop, $
                     SET_DROPLIST_SELECT=0, SET_VALUE=dataName[0]
                  WIDGET_CONTROL, sMainState.sSurfState.wSurfShadeDrop, $
                     SET_DROPLIST_SELECT=0, SET_VALUE='Light-source'

                  *(sMainState.hDataName) = dataName
                  *(sMainState.sThreshState.hMinData) = minData
                  *(sMainState.sThreshState.hMaxData) = maxData
                  *(sMainState.sThreshState.hTransVal) = minData
                  *(sMainState.sThreshState.hLowThresh) = minData
                  *(sMainState.sThreshState.hHighThresh) = maxData

                  sMainState.szData = szData
                  sMainState.sViewState = $
                     Viz3D_View(sMainState.sViewState.viewWin, $
                        XMAX=(szData[1]-1), $
                        YMAX=(szData[2]-1), $
                        ZMAX=(szData[3]-1), $
                        ANG1=sMainState.sViewState.ang1, $
                        ANG2=sMainState.sViewState.ang2, $
                        ANG3=sMainState.sViewState.ang3, $
                        DIR1=sMainState.sViewState.dir1, $
                        DIR2=sMainState.sViewState.dir2, $
                        DIR3=sMainState.sViewState.dir3, $
                        ZOOM=sMainState.sViewState.zoomFac , $
                        ZSCALE=sMainState.sViewState.zScale , $
                        PERSP=sMainState.sViewState.pDist)

                  sMainState.sBlockState.c1 = $
                     [szData[1]-1, szData[2]-1, szData[3]-1] / 3
                  sMainState.sBlockState.c2 = $
                     (2 * [szData[1]-1, szData[2]-1, szData[3]-1]) / 3

                  sMainState.sProfState.x1Ortho = FLOAT(szData[1] - 1L) / 2.0
                  sMainState.sProfState.y1Ortho = FLOAT(szData[2] - 1L) / 2.0
                  sMainState.sProfState.z1Ortho = 0.0
                  sMainState.sProfState.x2Ortho = FLOAT(szData[1] - 1L) / 2.0
                  sMainState.sProfState.y2Ortho = FLOAT(szData[2] - 1L) / 2.0
                  sMainState.sProfState.z2Ortho = FLOAT(szData[3] - 1L)
                  sMainState.sProfState.x1Obliq = FLOAT(szData[1] - 1L) / 2.0
                  sMainState.sProfState.y1Obliq = FLOAT(szData[2] - 1L) / 2.0
                  sMainState.sProfState.z1Obliq = 0.0
                  sMainState.sProfState.x2Obliq = FLOAT(szData[1] - 1L) / 2.0
                  sMainState.sProfState.y2Obliq = FLOAT(szData[2] - 1L) / 2.0
                  sMainState.sProfState.z2Obliq = FLOAT(szData[3] - 1L)

                  sMainState.sProbeState.x = FLOAT(szData[1] - 1L) / 2.0
                  sMainState.sProbeState.y = FLOAT(szData[2] - 1L) / 2.0
                  sMainState.sProbeState.z = FLOAT(szData[3] - 1L) / 2.0

                  ; Clean out the display list.
                  for i=0, N_ELEMENTS(*(sMainState.hDisplayList)) -1 do begin
                     hGraphic = (*(sMainState.hDisplayList))[i]
                     IF (TAG_NAMES( *hGraphic, /STRUCTURE_NAME ) EQ 'VIZ3D_SURF') $
                     THEN BEGIN
                        PTR_FREE, (*hGraphic).hVertList
                        PTR_FREE, (*hGraphic).hPolyList
                        PTR_FREE, (*hGraphic).hColorList
                     ENDIF
                     IF (TAG_NAMES( *hGraphic, /STRUCTURE_NAME ) EQ $
                        'VIZ3D_OBLIQ_PLANE') THEN BEGIN
                        PTR_FREE, (*hGraphic).hVertPlane
                        PTR_FREE, (*hGraphic).hVert3D
                     ENDIF
                     PTR_FREE, hGraphic
;                    PTR_FREE, (*(sMainState.hDisplayList))[i]
                  endfor
                  PTR_FREE, sMainState.hDisplayList
                  sMainState.hDisplayList = PTR_NEW(/ALLOCATE_HEAP)
                  ; Repair "Delete" menu.
                  delBttn = WIDGET_INFO(sMainState.wDeleteMenu, /CHILD)
                  WHILE (WIDGET_INFO(delBttn, /VALID_ID)) DO BEGIN
                     WIDGET_CONTROL, delBttn, /DESTROY
                     delBttn = WIDGET_INFO(sMainState.wDeleteMenu, /CHILD)
                  ENDWHILE
                  ; Erase.
                  SET_PLOT, 'Z'
                  ERASE
                  Viz3D_DrawData, sMainState
                  sMainState.cleanupBuffer = 1
                  Viz3D_ThreshShow, sMainState
                  Viz3D_SurfShow, sMainState, /CALC_HIST
                  Viz3D_CleanBuffer, sMainState
                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
                  WIDGET_CONTROL, sMainState.sProbeState.wProbeXText, $
                     SET_VALUE=''
                  WIDGET_CONTROL, sMainState.sProbeState.wProbeYText, $
                     SET_VALUE=''
                  WIDGET_CONTROL, sMainState.sProbeState.wProbeZText, $
                     SET_VALUE=''

               endif else begin

                  ; Additional data.

                  minData = MIN(data3D, MAX=maxData)
                  minData = DOUBLE(minData)
                  maxData = DOUBLE(maxData)
                  if (minData eq maxData) then maxData[0] = maxData[0] + 1.0D

                  bSize = (maxData - minData) / 200.0
                  n_samples = N_ELEMENTS(data3D)
                  IF (n_samples GT 32767L) THEN BEGIN
                     n_samples = 32767L + ROUND(SQRT(n_samples - 32767L))
                     s_index = RANDOMU(s, 1)
                     s_index = RANDOMU(s, n_samples)
                     s_index = LONG(TEMPORARY(s_index) * FLOAT(n_samples))
                     hist_data = FLOAT(data3D[TEMPORARY(s_index)])
                     dataHist = $
                        HISTOGRAM(TEMPORARY(hist_data), MIN=minData, MAX=maxData, $
                           BINSIZE=bSize) > 1.0
                  ENDIF ELSE BEGIN
                     dataHist = $
                        HISTOGRAM(FLOAT(data3D), MIN=minData, MAX=maxData, $
                           BINSIZE=bSize) > 1.0
                  ENDELSE

                  dataHist = ALOG(FLOAT(TEMPORARY(dataHist))) > 0.0
                  lDataHist = *(sMainState.sThreshState.hDataHist)
                  lDataHist = [TEMPORARY(lDataHist), $
                     PTR_NEW(dataHist)]
                  *(sMainState.sThreshState.hDataHist) = lDataHist
                  lRangeHist = *(sMainState.sSurfState.hRangeHist)
                  lRangeHist = [TEMPORARY(lRangeHist), $
                     PTR_NEW(dataHist, /NO_COPY)]
                  *(sMainState.sSurfState.hRangeHist) = lRangeHist

                  lData3D = *(sMainState.hDataList)
                  lData3D = [TEMPORARY(lData3D), $
                     PTR_NEW(data3D, /NO_COPY)]
                  *(sMainState.hDataList) = lData3D
                  dataNames = *(sMainState.hDataName)
                  dataNames = [TEMPORARY(dataNames), dataName]

                  WIDGET_CONTROL, sMainState.wDataModeBase, SENSITIVE=1
                  WIDGET_CONTROL, sMainState.sSurfState.wSurfShadeBase, $
                     SENSITIVE=1
                  WIDGET_CONTROL, sMainState.wDataDrop, $
                     SET_DROPLIST_SELECT=0, SET_VALUE=dataNames
                  sName = dataNames
                  sName[0] = 'Light-source'
                  WIDGET_CONTROL, sMainState.sSurfState.wSurfShadeDrop, $
                     SET_DROPLIST_SELECT=0, SET_VALUE=sName

                  *(sMainState.hDataName) = dataNames

                  lMinData = *(sMainState.sThreshState.hMinData)
                  lMaxData = *(sMainState.sThreshState.hMaxData)
                  lMinData = [TEMPORARY(lMinData), minData]
                  lMaxData = [TEMPORARY(lMaxData), maxData]

                  surfThresh = (lMinData + lMaxData) / 2.0D
                  *(sMainState.sSurfState.hSurfThresh) = surfThresh

                  *(sMainState.sThreshState.hTransVal) = lMinData
                  *(sMainState.sThreshState.hLowThresh) = lMinData
                  *(sMainState.sThreshState.hHighThresh) = lMaxData

                  *(sMainState.sThreshState.hMinData) = lMinData
                  *(sMainState.sThreshState.hMaxData) = lMaxData

               endelse
            endif else begin
               goRead = 0
            endelse

         endwhile

         CLOSE, lun
         FREE_LUN, lun

      end

      'wSaveTiffBttn': begin ; Save tiff image.
        if (LMGR(/DEMO)) then begin
           tmp = DIALOG_MESSAGE( /ERROR, $
                 'WRITE_TIFF: Feature disabled for demo mode.')
            WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
            return
         endif
         tiffFile = DIALOG_PICKFILE(GROUP=event.top, /WRITE, FILTER='*.tif', $
                             FILE='slicer3.tif')
         WIDGET_CONTROL, /HOURGLASS
         if (tiffFile[0] eq '') then begin
            WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
            return
         endif
         tiffFiles = FILE_SEARCH(tiffFile)
         if (tiffFiles[0] ne '') then begin
            ans = DIALOG_MESSAGE('File exists.   Overwrite ?', /QUESTION)
            WIDGET_CONTROL, /HOURGLASS
            if (ans ne 'Yes') then begin
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               return
            endif
         endif
         WSET, sMainState.pixWin
         if (sMainState.sColorState.displayBits eq 24) then begin
            WRITE_TIFF, tiffFile[0], TVRD(/TRUE), ORIENTATION=4
         endif else begin
            image = TVRD()
            TVLCT, cR, cG, cB, /GET
            WRITE_TIFF, tiffFile[0], TEMPORARY(image), $
                        RED=cR, GREEN=cG, BLUE=cB, ORIENTATION=4
         endelse
         WSET, sMAinState.mainWin
      end

      'wSaveSubsetBttn': begin ; Save subset.
        if (LMGR(/DEMO)) then begin
           tmp = DIALOG_MESSAGE( /ERROR, $
                 'OPENW: Feature disabled for demo mode.')
            WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
            return
         endif
         dataFile = DIALOG_PICKFILE(GROUP=event.top, /WRITE, FILTER='*.dat', $
                             FILE='slicer3.dat')
         WIDGET_CONTROL, /HOURGLASS
         if (dataFile[0] eq '') then begin
            WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
            return
         endif
         dataFiles = FILE_SEARCH(dataFile)
         if (dataFiles[0] ne '') then begin
            ans = DIALOG_MESSAGE('File exists.   Overwrite ?', /QUESTION)
            WIDGET_CONTROL, /HOURGLASS
            if (ans ne 'Yes') then begin
               WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
               return
            endif
         endif

         x1 = sMainState.sBlockState.c1[0] < sMainState.sBlockState.c2[0]
         y1 = sMainState.sBlockState.c1[1] < sMainState.sBlockState.c2[1]
         z1 = sMainState.sBlockState.c1[2] < sMainState.sBlockState.c2[2]
         x2 = sMainState.sBlockState.c1[0] > sMainState.sBlockState.c2[0]
         y2 = sMainState.sBlockState.c1[1] > sMainState.sBlockState.c2[1]
         z2 = sMainState.sBlockState.c1[2] > sMainState.sBlockState.c2[2]

         GET_LUN, lun
         OPENW, lun, dataFile

         lData3D = *(sMainState.hDataList)
         dataNames = *(sMainState.hDataName)

         for i=0, N_ELEMENTS(lData3D)-1L do begin
            WRITEU, lun, SIZE((*(lData3D[i]))[x1:x2, y1:y2, z1:z2])
            WRITEU, lun, STRLEN(dataNames[i])
            WRITEU, lun, BYTE(dataNames[i])
            WRITEU, lun, (*(lData3D[i]))[x1:x2, y1:y2, z1:z2]
         endfor

         *(sMainState.hDataName) = dataNames
         *(sMainState.hDataList) = lData3D

         CLOSE, lun
         FREE_LUN, lun
      end

      'wOptionsBttn': begin ; Options.
         wOptBase = WIDGET_BASE(TITLE='Slicer3', GROUP_LEADER=event.top, $
                       /COLUMN, XPAD=1, YPAD=1, SPACE=1, /MODAL)

         wOptAxisBase = WIDGET_BASE(wOptBase, /ROW, /FRAME, $
                                    XPAD=1, YPAD=1, SPACE=1)
         wOptAxisLab = WIDGET_LABEL(wOptAxisBase, VALUE='Axis')
         wOptAxisExBase = WIDGET_BASE(wOptAxisBase, /ROW, /EXCLUSIVE, $
                                      XPAD=1, YPAD=1, SPACE=1)
         wOptAxisOnBttn = WIDGET_BUTTON(wOptAxisExBase, VALUE='On', $
                             UVALUE='wOptAxisOnBttn', /NO_RELEASE)
         wOptAxisOffBttn = WIDGET_BUTTON(wOptAxisExBase, VALUE='Off', $
                              UVALUE='wOptAxisOffBttn', /NO_RELEASE)
         if (sMainState.axisOn) then $
            WIDGET_CONTROL, wOptAxisOnBttn, /SET_BUTTON $
         else WIDGET_CONTROL, wOptAxisOffBttn, /SET_BUTTON

         wOptCubeBase = WIDGET_BASE(wOptBase, /ROW, /FRAME, $
                                    XPAD=1, YPAD=1, SPACE=1)
         wOptCubeLab = WIDGET_LABEL(wOptCubeBase, VALUE='Cube')
         wOptCubeExBase = WIDGET_BASE(wOptCubeBase, /ROW, /EXCLUSIVE, $
                                      XPAD=1, YPAD=1, SPACE=1)
         wOptCubeOnBttn = WIDGET_BUTTON(wOptCubeExBase, VALUE='On', $
                             UVALUE='wOptCubeOnBttn', /NO_RELEASE)
         wOptCubeOffBttn = WIDGET_BUTTON(wOptCubeExBase, VALUE='Off', $
                              UVALUE='wOptCubeOffBttn', /NO_RELEASE)
         if (sMainState.cubeOn) then $
            WIDGET_CONTROL, wOptcubeOnBttn, /SET_BUTTON $
         else WIDGET_CONTROL, wOptcubeOffBttn, /SET_BUTTON

         wOptSizeBase = WIDGET_BASE(wOptBase, /ROW, XPAD=1, YPAD=1, $
                                    SPACE=1)
         wOptSizeLab = WIDGET_LABEL(wOptSizeBase, VALUE='Window Size')
         wOptSizeText = WIDGET_TEXT(wOptSizeBase, UVALUE='wOptSizeText', $
                           VALUE=STRING(sMAinState.winX), /EDITABLE)

         wOptBttnBase = WIDGET_BASE(wOptBase, /ROW, XPAD=1, YPAD=1, $
                                    SPACE=1)
         wOptOkBttn = WIDGET_BUTTON(wOptBttnBase, VALUE='Ok', $
                         UVALUE='wOptOkBttn', /ALIGN_LEFT)
         wOptCanBttn = WIDGET_BUTTON(wOptBttnBase, VALUE='Cancel', $
                          UVALUE='wOptCanBttn', /ALIGN_RIGHT)

         sOptState = {SOptState, Ok:0, axisOn:sMainState.axisOn, $
                      cubeOn:sMainState.cubeOn, winXY:sMainState.winX, $
                      wOptSizeText:wOptSizeText}
         hState = PTR_NEW(sOptState, /NO_COPY)
         WIDGET_CONTROL, wOptBase, SET_UVALUE=hState

         WIDGET_CONTROL, wOptBase, /REALIZE
         XMANAGER, 'Viz3D_Event', wOptBase, $
            EVENT_HANDLER='Viz3D_OptEvent'
         WIDGET_CONTROL, /HOURGLASS

         sOptState = *hState
         PTR_FREE, hState
         if (sOptState.Ok) then begin ; User clicked Ok.

            if ((sOptState.winXY ne sMainState.winX) or $
                (sOptState.winXY ne sMainState.winY)) then begin
               ; Resize window.
               sMainState.winX = sOptState.winXY
               sMainState.winY = sOptState.winXY
               sMainState.cubeOn = sOptState.cubeOn
               sMainState.axisOn = sOptState.axisOn

               SET_PLOT, 'Z'
               DEVICE, SET_RESOLUTION=[sMainState.winX, sMainState.winY], $
                       /Z_BUFFERING
               SET_PLOT, sMainState.screenDevice
               WDELETE, sMainState.pixWin
               WINDOW, /FREE, /PIXMAP, XSIZE=sMainState.winX, $
                  YSIZE=sMainState.winY, RETAIN=2
               sMainState.pixWin = !D.Window

               WIDGET_CONTROL, sMainState.wMainDraw, /DESTROY
               sMainState.wMainDraw = WIDGET_DRAW(sMainState.wDrawBase, $
                  XSIZE=sMainState.winX, YSIZE=sMainState.winY, RETAIN=2, $
                  UVALUE='wMainDraw', /BUTTON_EVENTS)
               WIDGET_CONTROL, sMainState.wMainDraw, GET_VALUE=mainWin
               sMainState.mainWin = mainWin
               WSET, sMainState.mainWin

               Viz3D_FillDepth, sMainState
               Viz3D_Redraw, sMainState
            endif else begin
               if (((sMainState.cubeOn eq 1) and (sOptState.cubeOn eq 0)) or $
                   ((sMainState.axisOn eq 1) and (sOptState.axisOn eq 0))) $
               then begin
                  sMainState.cubeOn = sOptState.cubeOn
                  sMainState.axisOn = sOptState.axisOn
                  Viz3D_Redraw, sMainState
               endif else begin
                  sMainState.cubeOn = sOptState.cubeOn
                  sMainState.axisOn = sOptState.axisOn
                  Viz3D_DrawData, sMainState
               endelse
            endelse
         endif
      end

      'wEraseBttn': begin ; Erase.
         ; Clean out the display list.
         for i=0, N_ELEMENTS(*(sMainState.hDisplayList)) -1 do begin
            hGraphic = (*(sMainState.hDisplayList))[i]
            IF (TAG_NAMES( *hGraphic, /STRUCTURE_NAME ) EQ 'VIZ3D_SURF') $
            THEN BEGIN
               PTR_FREE, (*hGraphic).hVertList
               PTR_FREE, (*hGraphic).hPolyList
               PTR_FREE, (*hGraphic).hColorList
            ENDIF
            IF (TAG_NAMES( *hGraphic, /STRUCTURE_NAME ) EQ $
               'VIZ3D_OBLIQ_PLANE') THEN BEGIN
               PTR_FREE, (*hGraphic).hVertPlane
               PTR_FREE, (*hGraphic).hVert3D
            ENDIF
            PTR_FREE, hGraphic
         endfor
         PTR_FREE, sMainState.hDisplayList
         sMainState.hDisplayList = PTR_NEW(/ALLOCATE_HEAP)
         if (sMainState.actMode eq 7) then begin
            ; View mode, redraw small slice and block windows.
            Viz3D_SliceShow, sMainState
            Viz3D_BlockShow, sMainState
         endif
         ; Repair "Delete" menu.
         delBttn = WIDGET_INFO(sMainState.wDeleteMenu, /CHILD)
         WHILE (WIDGET_INFO(delBttn, /VALID_ID)) DO BEGIN
            WIDGET_CONTROL, delBttn, /DESTROY
            delBttn = WIDGET_INFO(sMainState.wDeleteMenu, /CHILD)
         ENDWHILE
         ; Erase.
         SET_PLOT, 'Z'
         ERASE
         Viz3D_DrawData, sMainState
      end

      'wDeleteBttn': begin ; Delete a graphic.
         ; Determine which button in the menu was chosen
         widBttn = WIDGET_INFO(sMainState.wDeleteMenu, /Child)
         hGraphic = (*(sMainState.hDisplayList))[0]
         indx = 0
         WHILE (widBttn ne event.id) do begin
            widBttn = WIDGET_INFO(widBttn, /SIBLING)
            indx = indx +1
            hGraphic = (*(sMainState.hDisplayList))[indx]
         ENDWHILE
         ; Delete and redraw.
         IF (TAG_NAMES( *hGraphic, /STRUCTURE_NAME ) $
             EQ 'VIZ3D_SURF') THEN BEGIN
            PTR_FREE, (*(hGraphic)).hVertList
            PTR_FREE, (*(hGraphic)).hPolyList
            PTR_FREE, (*(hGraphic)).hColorList
         ENDIF
         IF (TAG_NAMES( *hGraphic, /STRUCTURE_NAME ) EQ $
            'VIZ3D_OBLIQ_PLANE') THEN BEGIN
            PTR_FREE, (*hGraphic).hVertPlane
            PTR_FREE, (*hGraphic).hVert3D
         ENDIF
         PTR_FREE, hGraphic
         listCount = N_ELEMENTS(*(sMainState.hDisplayList))
         if (listCount GT 1) then begin
           if (indx EQ 0) then begin
              *(sMainState.hDisplayList) = $
                 (*(sMainState.hDisplayList))[1:listCount-1]
           endif else if (indx EQ listCount-1) then begin
              *(sMainState.hDisplayList) = $
                 (*(sMainState.hDisplayList))[0:listCount-2]
           endif else begin
              ; general case
              *(sMainState.hDisplayList) = $
                 (*(sMainState.hDisplayList))[[indgen(indx), $
                              indgen(listCount-indx-1)+indx+1]]
           endelse
         ; the list is now empty, the listpointer itself points to undefined
         endif else begin
            PTR_FREE, sMainState.hDisplayList
            sMainState.hDisplayList = PTR_NEW(/ALLOCATE_HEAP)
         endelse
         WIDGET_CONTROL, widBttn, /DESTROY
         Viz3D_Redraw, sMainState
      end

      'wColResetBttn': begin ; Reset Colors.
         sMainState.sColorState = Viz3D_LoadColor(DIFFSHADE=20)
         WSET, sMainState.mainWin
         Viz3D_DrawData, sMainState
      end

      'wColDiffBttn': begin ; Differential shading.
         diffBase = WIDGET_BASE(TITLE='Slicer3', XPAD=1, YPAD=1, SPACE=1, $
            /COLUMN, UVALUE=event.top, GROUP_LEADER=event.top, /MODAL)
         wDiffSlid = WIDGET_SLIDER(diffBase, MIN=0, MAX=100, /DRAG, $
                        TITLE='Differential Shading (%)', $
                        VALUE=sMainState.sColorState.diffShade, $
                        UVALUE='wDiffSlid')
         wDiffCloseBttn = WIDGET_BUTTON(diffBase, VALUE='Close', $
                             UVALUE='wDiffCloseBttn')
         WIDGET_CONTROL, diffBase, /REALIZE
         WIDGET_CONTROL, event.top, SET_UVALUE=sMainState, /NO_COPY
         XMANAGER, 'Viz3D_Event', diffBase, $
            EVENT_HANDLER='Viz3D_DiffShadeEvent'
         WIDGET_CONTROL, event.top, GET_UVALUE=sMainState, /NO_COPY
         Viz3D_DrawData, sMainState
      end
      'wColSliceBttn': begin ; Slice/block colors.
         saveP = !P
         saveX = !X
         saveY = !Y
         saveZ = !Z
         XLOADCT, BOTTOM=0, /USE_CURRENT, $
            NCOLORS=sMainState.sColorState.nColor, GROUP=event.top, /MODAL
         WIDGET_CONTROL, /HOURGLASS
         WSET, sMainState.mainWin
         !P = TEMPORARY(saveP)
         !X = TEMPORARY(saveX)
         !Y = TEMPORARY(saveY)
         !Z = TEMPORARY(saveZ)
         sViz3DColors = sMainState.sColorState
         TVLCT, r, g, b, /GET
         sViz3DColors.rSlice = r[0:sMainState.sColorState.nColor-1]
         sViz3DColors.gSlice = g[0:sMainState.sColorState.nColor-1]
         sViz3DColors.bSlice = b[0:sMainState.sColorState.nColor-1]
         Viz3D_DiffColor, sViz3DColors
         sMainState.sColorState = TEMPORARY(sViz3DColors)
         Viz3D_DrawData, sMainState
      end
      'wColSurfBttn': begin ; Surface colors.
         saveP = !P
         saveX = !X
         saveY = !Y
         saveZ = !Z
         XLOADCT, BOTTOM=(3*sMainState.sColorState.nColor), /USE_CURRENT, $
            NCOLORS=sMainState.sColorState.nColor, GROUP=event.top, /MODAL
         WIDGET_CONTROL, /HOURGLASS
         WSET, sMainState.mainWin
         !P = TEMPORARY(saveP)
         !X = TEMPORARY(saveX)
         !Y = TEMPORARY(saveY)
         !Z = TEMPORARY(saveZ)
         Viz3D_DrawData, sMainState
      end
      'wColProjBttn': begin ; Projection colors.
         saveP = !P
         saveX = !X
         saveY = !Y
         saveZ = !Z
         XLOADCT, BOTTOM=(4*sMainState.sColorState.nColor), /USE_CURRENT, $
            NCOLORS=sMainState.sColorState.nColor, GROUP=event.top, /MODAL
         WIDGET_CONTROL, /HOURGLASS
         WSET, sMainState.mainWin
         !P = TEMPORARY(saveP)
         !X = TEMPORARY(saveX)
         !Y = TEMPORARY(saveY)
         !Z = TEMPORARY(saveZ)
         Viz3D_DrawData, sMainState
      end

      'wAnimateBttn': begin ; Animate
         ; *** Future animation functionality.
      end

      'wHelpBttn': begin ; Help
         helpFileName = filepath("idl_demo.hlp", $
                                 SUBDIR=['examples','demo','demohelp'])
         dummy=FILE_SEARCH(helpFileName, count=count)
         if (count GT 0) then begin
            topicNum = 3250 ;; slicer
            ONLINE_HELP, topicNum, /CONTEXT, $
               book=helpFileName, $
               /FULL_PATH
         endif else begin
            XDISPLAYFILE, FILEPATH("slicer3.txt", $
                SUBDIR=['help', 'widget']), $
                TITLE="Slicer3 Help", $
                GROUP=event.top, $
                /MODAL, $
                WIDTH=72, HEIGHT=24
         endelse
      end

      'wDataDrop': begin ; Set data.
         if (event.index ne sMainState.curData) then begin
            sMainState.curData = event.index
            sMainState.sSurfState.curShade = event.index
            dName = *(sMainState.hDataName)
            sName = dName
            sName[event.index] = 'Light-source'
            WIDGET_CONTROL, sMainState.sSurfState.wSurfShadeDrop, $
               SET_VALUE=sName, /NO_COPY
            WIDGET_CONTROL, sMainState.sSurfState.wSurfShadeDrop, $
               SET_DROPLIST_SELECT=event.index
            Viz3D_SurfShow, sMainState, /CALC_HIST
            Viz3D_ThreshShow, sMainState
         endif
      end

      'wModeDrop': begin ; Set mode.
         case event.index of
            0: begin ; Slice mode.
               if (sMainState.actMode ne 0) then begin
                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
                  WIDGET_CONTROL, sMainState.wCurMapBase, MAP=0
                  WIDGET_CONTROL, sMainState.wSliceBase, /MAP
                  sMainState.wCurMapBase = sMainState.wSliceBase
                  sMainState.actMode = 0
                  WIDGET_CONTROL, sMainState.wDrawBase, $
                     EVENT_PRO='Viz3D_SliceEvent'
                  WIDGET_CONTROL, sMainState.wSaveSubsetBttn, SENSITIVE=0
               endif
            end
            1: begin ; Block mode.
               if (sMainState.actMode ne 1) then begin
                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
                  WIDGET_CONTROL, sMainState.wCurMapBase, MAP=0
                  WIDGET_CONTROL, sMainState.wBlockBase, /MAP
                  sMainState.wCurMapBase = sMainState.wBlockBase
                  sMainState.actMode = 1
                  WIDGET_CONTROL, sMainState.wDrawBase, $
                     EVENT_PRO='Viz3D_BlockEvent'
                  Viz3D_BlockShow, sMainState
                  Viz3D_BlockShow, sMainState, /DIRECT

                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=('(' + $
                     STRTRIM(STRING(sMainState.sBlockState.c1[0]),2) + $
                     ', ' + $
                     STRTRIM(STRING(sMainState.sBlockState.c1[1]),2) + $
                     ', ' + $
                     STRTRIM(STRING(sMainState.sBlockState.c1[2]),2) + $
                     '), (' + $
                     STRTRIM(STRING(sMainState.sBlockState.c2[0]),2) + $
                     ', ' + $
                     STRTRIM(STRING(sMainState.sBlockState.c2[1]),2) + $
                     ', ' + $
                     STRTRIM(STRING(sMainState.sBlockState.c2[2]),2) + ')')

                  sMainState.cleanupView = 1
                  WIDGET_CONTROL, sMainState.wSaveSubsetBttn, SENSITIVE=1
               endif
            end
            2: begin ; Surface mode.
               if (sMainState.actMode ne 2) then begin
                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
                  WIDGET_CONTROL, sMainState.wCurMapBase, MAP=0
                  WIDGET_CONTROL, sMainState.wSurfBase, /MAP
                  sMainState.wCurMapBase = sMainState.wSurfBase
                  sMainState.actMode = 2
                  WIDGET_CONTROL, sMainState.wDrawBase, $
                     EVENT_PRO='Viz3D_SurfEvent'
                  WIDGET_CONTROL, sMainState.wSaveSubsetBttn, SENSITIVE=0
               endif
            end
            3: begin ; Projection mode.
               if (sMainState.actMode ne 4) then begin
                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
                  WIDGET_CONTROL, sMainState.wCurMapBase, MAP=0
                  WIDGET_CONTROL, sMainState.wProjectBase, /MAP
                  sMainState.wCurMapBase = sMainState.wProjectBase
                  sMainState.actMode = 4
                  WIDGET_CONTROL, sMainState.wDrawBase, $
                     EVENT_PRO='Viz3D_ProjectEvent'
                  WIDGET_CONTROL, sMainState.wSaveSubsetBttn, SENSITIVE=0
               endif
            end
            4: begin ; Threshold mode.
               if (sMainState.actMode ne 3) then begin
                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
                  WIDGET_CONTROL, sMainState.wCurMapBase, MAP=0
                  WIDGET_CONTROL, sMainState.wThreshBase, /MAP
                  sMainState.wCurMapBase = sMainState.wThreshBase
                  sMainState.actMode = 3
                  WIDGET_CONTROL, sMainState.wDrawBase, $
                     EVENT_PRO='Viz3D_ThreshEvent'
                  WIDGET_CONTROL, sMainState.wSaveSubsetBttn, SENSITIVE=0
               endif
            end
            5: begin ; Profile mode.
               if (sMainState.actMode ne 5) then begin
                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
                  WIDGET_CONTROL, sMainState.wCurMapBase, MAP=0
                  WIDGET_CONTROL, sMainState.wProfileBase, /MAP
                  sMainState.wCurMapBase = sMainState.wProfileBase
                  sMainState.actMode = 5
                  WIDGET_CONTROL, sMainState.wDrawBase, $
                     EVENT_PRO='Viz3D_ProfileEvent'
                  Viz3D_ProfShow, sMainState
                  Viz3D_ProfDraw, sMainState
                  sMainState.cleanupView = 1
                  WIDGET_CONTROL, sMainState.wSaveSubsetBttn, SENSITIVE=0
               endif
            end
            6: begin ; Probe mode.
               if (sMainState.actMode ne 6) then begin
                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
                  WIDGET_CONTROL, sMainState.wCurMapBase, MAP=0
                  WIDGET_CONTROL, sMainState.wProbeBase, /MAP
                  sMainState.wCurMapBase = sMainState.wProbeBase
                  sMainState.actMode = 6
                  WIDGET_CONTROL, sMainState.wDrawBase, $
                     EVENT_PRO='Viz3D_ProbeEvent'
                  sMainState.cleanupView = 1
                  WIDGET_CONTROL, sMainState.wSaveSubsetBttn, SENSITIVE=0
                  Viz3D_ProbeDraw, sMainState
               endif
            end
            7: begin ; View mode.
               if (sMainState.actMode ne 7) then begin
                  WIDGET_CONTROL, sMainState.wStatText, SET_VALUE=''
                  WIDGET_CONTROL, sMainState.wCurMapBase, MAP=0
                  WIDGET_CONTROL, sMainState.wViewBase, /MAP
                  sMainState.wCurMapBase = sMainState.wViewBase
                  sMainState.actMode = 7
                  WIDGET_CONTROL, sMainState.wDrawBase, $
                     EVENT_PRO='Viz3D_ViewEvent'
                  WIDGET_CONTROL, sMainState.wSaveSubsetBttn, SENSITIVE=0
               endif
            end
         endcase
      end
      else:
   endcase

   WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY
   return

IO_BAD:

   if (NOT(PTR_VALID(*(sMainState.hDataList)))) then begin
      ans = DIALOG_MESSAGE(['Unrecoverable I/O error.', $
                            'Exiting Slicer3.'], /ERROR)
      PTR_FREE, sMainState.hMain
      WIDGET_CONTROL, event.top, /DESTROY
      return
   endif

   if (N_ELEMENTS(sMainState) gt 0L) then $
      WIDGET_CONTROL, mainID, SET_UVALUE=sMainState, /NO_COPY

   if (n_elements(fLun) gt 0L) then begin
      CLOSE, fLun
      FREE_LUN, fLun
   endif

   ans = DIALOG_MESSAGE('Unknown I/O error.', /ERROR)

end
;******************************************************************************


;******************************************************************************
; Procedure to clean up the main draw window if it needs it.

pro Viz3D_CleanView, sMainState, SKIP_BUFFER=skipBuffer

   COMPILE_OPT hidden, idl2

   if (not(KEYWORD_SET(skipBuffer))) then $
      Viz3D_CleanBuffer, sMainState

   if (sMainState.cleanupView) then begin
      WSET, sMainState.mainWin
      DEVICE, COPY=[0, 0, sMainState.winX, sMainState.winY, $
                    0, 0, sMainState.pixWin]
      sMainState.cleanupView = 0
   endif
end
;******************************************************************************


;******************************************************************************
; Procedure to clean up (redraw) the Z buffer (and other windows) if needed.

pro Viz3D_CleanBuffer, sMainState
   COMPILE_OPT hidden, idl2

   if (sMainState.cleanupBuffer) then begin
      Viz3D_FillDepth, sMainState
      Viz3D_SliceShow, sMainState
      Viz3D_BlockShow, sMainState
      Viz3D_SurfShow, sMainState
      Viz3D_ThreshShow, sMainState, /DYNAMIC
      Viz3D_ViewShow, sMainState
      Viz3D_ProfShow, sMainState
      Viz3D_Redraw, sMainState
   endif
end
;******************************************************************************


;******************************************************************************
; Cleanup handler for detached base.

pro Viz3D_KillDraw, wDrawBase

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, wDrawBase, GET_UVALUE=wMainBase, /NO_COPY


   WIDGET_CONTROL, wMainBase, /DESTROY

;  if (WIDGET_INFO(wMainBase, /VALID_ID)) then begin
;     WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY
;     WIDGET_CONTROL, wMainBase, KILL_NOTIFY=''
;     WIDGET_CONTROL, wMainBase, /DESTROY
;     PTR_FREE, sMainState.hMain
;     WDELETE, sMainState.pixWin
;     WDELETE, sMainState.sProfState.profPix
;     WDELETE, sMainState.sSurfState.surfPix
;     WDELETE, sMainState.sThreshState.threshPix
;  endif

end
;******************************************************************************


;******************************************************************************
; Cleanup handler for main window.

pro Viz3D_KillMain, wMainBase

   COMPILE_OPT hidden, idl2

   WIDGET_CONTROL, wMainBase, GET_UVALUE=sMainState, /NO_COPY
   if (sMainState.detachBase) then begin
      IF WIDGET_INFO(sMainState.wDrawBase, /VALID) THEN BEGIN
         WIDGET_CONTROL, sMainState.wDrawBase, KILL_NOTIFY=''
         WIDGET_CONTROL, sMainState.wDrawBase, /DESTROY
      ENDIF
   end
   for i=0, N_ELEMENTS(*(sMainState.hDisplayList)) -1 do begin
      hGraphic = (*(sMainState.hDisplayList))[i]
      IF (TAG_NAMES(*hGraphic, /STRUCTURE_NAME ) $
            EQ 'VIZ3D_SURF') THEN BEGIN
         PTR_FREE, (*hGraphic).hVertList
         PTR_FREE, (*hGraphic).hPolyList
         PTR_FREE, (*hGraphic).hColorList
      ENDIF
      IF (TAG_NAMES( *hGraphic, /STRUCTURE_NAME ) EQ $
         'VIZ3D_OBLIQ_PLANE') THEN BEGIN
         PTR_FREE, (*hGraphic).hVertPlane
         PTR_FREE, (*hGraphic).hVert3D
      ENDIF
      PTR_FREE, hGraphic
;     PTR_FREE, (*(sMainState.hDisplayList))[i]
   endfor
   PTR_FREE, sMainState.hDisplayList
   FOR i=0, N_ELEMENTS(*(sMainState.sThreshState.hDataHist))-1 do begin
      PTR_FREE, (*(sMainState.sThreshState.hDataHist))[i]
   ENDFOR
   PTR_FREE, sMainState.sThreshState.hDataHist
   FOR i=0, N_ELEMENTS(*(sMainState.hDataList))-1 do begin
      PTR_FREE, (*(sMainState.hDataList))[i]
   ENDFOR
   PTR_FREE, sMainState.hDataList
   PTR_FREE, sMainState.hDataName
   PTR_FREE, sMainState.hFrontDepth
   PTR_FREE, sMainState.hBackDepth
   PTR_FREE, sMainState.hFrontImage
   PTR_FREE, sMainState.hBackImage
   PTR_FREE, sMainState.sThreshState.hMinData
   PTR_FREE, sMainState.sThreshState.hMaxData
   PTR_FREE, sMainState.sThreshState.hTransVal
   PTR_FREE, sMainState.sThreshState.hLowThresh
   PTR_FREE, sMainState.sThreshState.hHighThresh
   FOR i=0, N_ELEMENTS(*(sMainState.sSurfState.hRangeHist))-1 do begin
      PTR_FREE, (*(sMainState.sSurfState.hRangeHist))[i]
   ENDFOR
   PTR_FREE, sMainState.sSurfState.hRangeHist
   PTR_FREE, sMainState.sSurfState.hSurfThresh
   PTR_FREE, sMainState.hMain
   WDELETE, sMainState.pixWin
   WDELETE, sMainState.sProfState.profPix
   WDELETE, sMainState.sSurfState.surfPix
   WDELETE, sMainState.sThreshState.threshPix
end
;******************************************************************************


;******************************************************************************
; Main procedure (entry point).

pro Slicer3, hData3D, DATA_NAMES=dataNames, DETACH=detachView, $
             GROUP_LEADER=groupLead, MODAL=modal

COMPILE_OPT idl2

   ; Use the "Colors" common block to save the current colortable.
   common colors, r, g, b, cur_red, cur_green, cur_blue
   IF (N_ELEMENTS(r) LE 0L) THEN LOADCT, 0
   saveR = r
   saveG = g
   saveB = b
   save_curR = cur_red
   save_curG = cur_green
   save_curB = cur_blue

   ; Check the incoming data (if any).

   maxHand = N_ELEMENTS(hData3D) - 1L
   if (maxHand ge 0L) then begin
      minData = DBLARR(N_ELEMENTS(hData3D))
      maxData = minData

      for i=0L, maxHand do begin

         if NOT(PTR_VALID(hData3D[i])) then begin
            PRINT, "Slicer3: data pointer is not valid."
            return
         endif

         neData = N_ELEMENTS((*(hData3D[i])))
         if (neData le 0L) then begin
            PRINT, "Slicer3: data pointer does not contain valid data."
            return
         endif
         szData = SIZE((*(hData3D[i])))
         maxData[i] = MAX((*(hData3D[i])), MIN=minVal)
         minData[i] = minVal
         if (minData[i] eq maxData[i]) then maxData[i] = maxData[i] + 1.0D

         if (szData[0] ne 3L) then begin
            PRINT, "Slicer3: data does not have 3 dimensions."
            return
         endif else begin
            if (min(szData[1:3]) le 1L) then begin
               PRINT, $
                  "Slicer3: all 3 dimensions of data must be greater than 1."
               return
            endif
         endelse

         if (i eq 0) then sz1 = szData else begin
            diffDim = WHERE(szData[0:3] ne sz1[0:3])
            if (diffDim[0] ne (-1)) then begin
               PRINT, $
                  "Slicer3: all data sets must have the same dimensions."
               return
            endif
         endelse

      endfor
      defaultData = 0
   endif else begin
      data3D = BYTARR(2, 2, 2)
      szData = SIZE(data3D)
      minData = 0.0D
      maxData = 1.0D
      defaultData = 1
      dataNames = 'No Data'
      maxHand = 0
   endelse

   ; Main pointers.
   hMain = PTR_NEW(/ALLOCATE_HEAP)
   if (defaultData) then $
      hData3D = PTR_NEW(data3D, /NO_COPY)
   hDataList = PTR_NEW(hData3D)
   hDisplayList = PTR_NEW(/ALLOCATE_HEAP)

   ; Compute and store the histograms for each set of data.
   lDataHist = PTRARR(N_ELEMENTS(hData3D))
   lRangeHist = PTRARR(N_ELEMENTS(hData3D))
   for i=0L, maxHand do begin
      bSize = (maxData[i] - minData[i]) / 200.0
      n_samples = N_ELEMENTS((*(hData3D[i])))
      IF (n_samples GT 32767L) THEN BEGIN
         n_samples = 32767L + ROUND(SQRT(n_samples - 32767L))
         s_index = RANDOMU(s, 1)
         s_index = RANDOMU(s, n_samples)
         s_index = LONG(TEMPORARY(s_index) * FLOAT(n_samples))
         hist_data = FLOAT(((*(hData3D[i])))[TEMPORARY(s_index)])
         dataHist = $
            HISTOGRAM(TEMPORARY(hist_data), MIN=minData[i], MAX=maxData[i], $
               BINSIZE=bSize) > 1.0
      ENDIF ELSE BEGIN
         dataHist = $
            HISTOGRAM(FLOAT((*(hData3D[i]))), MIN=minData[i], MAX=maxData[i], $
               BINSIZE=bSize) > 1.0
      ENDELSE

      ; Histogram will be displayed as a LOG plot.
      dataHist = ALOG(FLOAT(TEMPORARY(dataHist))) > 0.0

      lDataHist[i] = PTR_NEW(dataHist)
      lRangeHist[i] = PTR_NEW(dataHist, /NO_COPY)
   endfor
   hDataHist = PTR_NEW(lDataHist, /NO_COPY)
   hRangeHist = PTR_NEW(lRangeHist, /NO_COPY)

   ; Store the name for each set of data.
   dName = STRARR(N_ELEMENTS(hData3D))
   for i=0L, maxHand do $
      if (i lt N_ELEMENTS(dataNames)) then $
         dName[i] = STRTRIM(STRING(dataNames[i]), 2) $
      else $
         dName[i] = 'Data ' + STRTRIM(STRING(i), 2)
   hDataName = PTR_NEW(dName)


   ; Save the current system variables.
   saveOrder = !Order
   saveP = !P
   saveX = !X
   saveY = !Y
   saveZ = !Z
   saveWin = !D.Window

   ; Save the original decomposed setting.
   DEVICE, GET_DECOMPOSED=saveDecomp

   ; Reset system variables to startup state.
   Viz3D_Reset

   ; Initialize parameters.

   DEVICE, GET_SCREEN_SIZE=screenSize
   winX = ((screenSize[0] / 2) < 640) > 320 ; View window size.
   winY = ((screenSize[1] / 2) < 640) > 320 ; View window size.
   winX = 8 * ((winX < winY) / 8)
   winY = winX

   detachBase = KEYWORD_SET(detachView)
	runModal = KEYWORD_SET(modal)
   ; Create the widgets.

; Generic widget_base keywords for modal or nonmodal
	extra = {TITLE:'IDL 3D Data Visualizer (Slicer3)', $
		ROW:1,SPACE:1,XPAD:1,YPAD:1}
	if (N_ELEMENTS(groupLead) LE 0L) then groupLead = 0L

; Make sure we have a group leader for modal. If not, create a dummy base.
; Creating a dummy base goes against the spirit of modal bases, which should
; always have a group leader for iconization and layering. But, for backwards
; compatibility we need to not throw an error...
	IF runModal THEN BEGIN
		IF NOT WIDGET_INFO(groupLead, /VALID_ID) THEN $
			groupLead = (dummyLeader = WIDGET_BASE())
		extra =  CREATE_STRUCT(extra,'MODAL',1)
	ENDIF

	if (WIDGET_INFO(groupLead, /VALID_ID)) then $
   		extra = CREATE_STRUCT(extra,'GROUP_LEADER',groupLead)

; Create the main base, either modal or not. If not, then get menu bar
	wMainBase = runModal ? WIDGET_BASE(/MODAL, _EXTRA=extra) : $
		WIDGET_BASE(APP_MBAR=wBarBase, _EXTRA=extra)

; Left-hand column, containing all the options and buttons
	wModeBase = WIDGET_BASE(wMainBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1)

; If no menu bar, then create a menu base for the buttons
	IF runModal THEN wBarBase = WIDGET_BASE(wModeBase, /ROW)
	suffix = runModal ? ' >' : ''

   WIDGET_CONTROL, wMainBase, /MANAGED
   wFileMenu = WIDGET_BUTTON(wBarBase, VALUE='File'+suffix, /MENU)
   wToolMenu = WIDGET_BUTTON(wBarBase, VALUE='Tools'+suffix, /MENU)
   wHelpMenu = WIDGET_BUTTON(wBarBase, VALUE='About'+suffix, /MENU, /HELP)
   wHelpBttn = WIDGET_BUTTON(wHelpMenu, VALUE='About Slicer', $
                             UVALUE='wHelpBttn')

   wLoadBttn = WIDGET_BUTTON(wFileMenu, VALUE='Load', UVALUE='wLoadBttn')

   wSaveMenu = WIDGET_BUTTON(wFileMenu, VALUE='Save', /MENU, /SEPARATOR)
   wSaveSubsetBttn = WIDGET_BUTTON(wSaveMenu, VALUE='Save Subset', $
                        UVALUE='wSaveSubsetBttn')
   WIDGET_CONTROL, wSaveSubsetBttn, SENSITIVE=0
   wSaveTiffBttn = WIDGET_BUTTON(wSaveMenu, VALUE='Save Tiff Image', $
                      UVALUE='wSaveTiffBttn')


   wQuitBttn = WIDGET_BUTTON(wFileMenu, VALUE='Quit', UVALUE='wQuitBttn', $
                             /SEPARATOR)

   wEraseBttn = WIDGET_BUTTON(wToolMenu, VALUE='Erase', $
                               UVALUE='wEraseBttn')
   wDeleteMenu = WIDGET_BUTTON(wToolMenu, VALUE='Delete', /MENU)

   wColorsMenu = WIDGET_BUTTON(wToolMenu, VALUE='Colors', /MENU)
   wColResetBttn = WIDGET_BUTTON(wColorsMenu, VALUE='Reset Colors', $
                     UVALUE='wColResetBttn')
   wColDiffBttn = WIDGET_BUTTON(wColorsMenu, VALUE='Differential Shading', $
                     UVALUE='wColDiffBttn')
   wColSliceBttn = WIDGET_BUTTON(wColorsMenu, VALUE='Slice/Block', $
                      UVALUE='wColSliceBttn')
   wColSurfBttn = WIDGET_BUTTON(wColorsMenu, VALUE='Surface', $
                     UVALUE='wColSurfBttn')
   wColProjBttn = WIDGET_BUTTON(wColorsMenu, VALUE='Projection', $
                     UVALUE='wColProjBttn')

;   *** Future animation functionality.
;   wAnimateBttn = WIDGET_BUTTON(wToolMenu, VALUE='Animate', $
;                     UVALUE='wAnimateBttn')

   wOptionsBttn = WIDGET_BUTTON(wToolMenu, VALUE='Options', $
                     UVALUE='wOptionsBttn')

   if (detachBase and NOT runModal) then $
      wDrawBase = WIDGET_BASE(TITLE='IDL 3-D Data Visualizer', $
         UVALUE=wMainBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
         EVENT_PRO='Viz3D_SliceEvent') $
   else $
      wDrawBase = WIDGET_BASE(wMainBase, UVALUE=wMainBase, /COLUMN, $
         SPACE=1, XPAD=1, YPAD=1, EVENT_PRO='Viz3D_SliceEvent')

   wStatText = WIDGET_TEXT(wDrawBase, SCR_XSIZE=winX-12, YSIZE=1, VALUE=' ')
   wMainDraw = WIDGET_DRAW(wDrawBase, XSIZE=winX, YSIZE=winY, $
      UVALUE='wMainDraw', RETAIN=2, /BUTTON_EVENTS)

   wDataModeBase = WIDGET_BASE(wModeBase, /ROW, /FRAME, $
                               SPACE=1, XPAD=1, YPAD=1)
   wDataLab = WIDGET_LABEL(wDataModeBase, VALUE='Data:')
   wDataDrop = WIDGET_DROPLIST(wDataModeBase, VALUE=dName, UVALUE='wDataDrop')
   if (N_ELEMENTS(dName) le 1L) then $
      WIDGET_CONTROL, wDataModeBase, SENSITIVE=0

   modes = ['Slice','Block','Surface','Projection', $
            'Threshold','Profile','Probe','View']
   wActModeBase = WIDGET_BASE(wModeBase, /ROW, /FRAME, $
                              SPACE=1, XPAD=1, YPAD=1)
   wDataLab = WIDGET_LABEL(wActModeBase, VALUE='Mode:')
   wModeDrop = WIDGET_DROPLIST(wActModeBase, VALUE=modes, UVALUE='wModeDrop')

   wMapBase = WIDGET_BASE(wModeBase, /FRAME, SPACE=1, XPAD=1, YPAD=1)

   wSliceBase = WIDGET_BASE(wMapBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
      EVENT_PRO='Viz3D_SliceEvent')
   wBlockBase = WIDGET_BASE(wMapBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
      MAP=0, EVENT_PRO='Viz3D_BlockEvent')
   wSurfBase = WIDGET_BASE(wMapBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
      MAP=0, EVENT_PRO='Viz3D_SurfEvent')
   wThreshBase = WIDGET_BASE(wMapBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
      MAP=0, EVENT_PRO='Viz3D_ThreshEvent')
   wProjectBase = WIDGET_BASE(wMapBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
      MAP=0, EVENT_PRO='Viz3D_ProjectEvent')
   wProfileBase = WIDGET_BASE(wMapBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
      MAP=0, EVENT_PRO='Viz3D_ProfileEvent')
   wProbeBase = WIDGET_BASE(wMapBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
      MAP=0, EVENT_PRO='Viz3D_ProbeEvent')
   wViewBase = WIDGET_BASE(wMapBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
      MAP=0, EVENT_PRO='Viz3D_ViewEvent')


   ; Slice base widgets.
   wSliceDraw = WIDGET_DRAW(wSliceBase, XSIZE=128, YSIZE=128, $
      RETAIN=2, UVALUE='wSliceDraw', /BUTTON_EVENTS)

   wBase = WIDGET_BASE(wSliceBase, /ROW, SPACE=1, XPAD=1, YPAD=1, $
              /EXCLUSIVE, /FRAME)
   wSliceDrawBttn = WIDGET_BUTTON(wBase, VALUE='Draw', $
      UVALUE='wSliceDrawBttn', /NO_RELEASE)
   wSliceExpoBttn = WIDGET_BUTTON(wBase, VALUE='Expose', $
      UVALUE='wSliceExpoBttn', /NO_RELEASE)
   WIDGET_CONTROL, wSliceDrawBttn, /SET_BUTTON

   wBase = WIDGET_BASE(wSliceBase, /COLUMN, SPACE=1, XPAD=1, YPAD=1, $
              /EXCLUSIVE, /FRAME)
   wSliceOrthoBttn = WIDGET_BUTTON(wBase, VALUE='Orthogonal', $
      UVALUE='wSliceOrthoBttn', /NO_RELEASE)
   wSliceObliqBttn = WIDGET_BUTTON(wBase, VALUE='Oblique', $
      UVALUE='wSliceObliqBttn', /NO_RELEASE)
   WIDGET_CONTROL, wSliceOrthoBttn, /SET_BUTTON

   wSliceMapBase = WIDGET_BASE(wSliceBase, SPACE=1, XPAD=1, YPAD=1, /FRAME)
   wSliceOrthoBase = WIDGET_BASE(wSliceMapBase, /ROW, SPACE=1, $
                        XPAD=1, YPAD=1, /EXCLUSIVE)
   wSliceXBttn = WIDGET_BUTTON(wSliceOrthoBase, VALUE='X', $
      UVALUE='wSliceXBttn', /NO_RELEASE)
   wSliceYBttn = WIDGET_BUTTON(wSliceOrthoBase, VALUE='Y', $
      UVALUE='wSliceYBttn', /NO_RELEASE)
   wSliceZBttn = WIDGET_BUTTON(wSliceOrthoBase, VALUE='Z', $
      UVALUE='wSliceZBttn', /NO_RELEASE)
   WIDGET_CONTROL, wSliceXBttn, /SET_BUTTON

   wSliceObliqBase = WIDGET_BASE(wSliceMapBase, /Column, SPACE=1, $
                        XPAD=1, YPAD=1, MAP=0)
   wSliceObliqBase1 = WIDGET_BASE(wSliceObliqBase, /ROW, SPACE=1, $
                        XPAD=1, YPAD=1, /EXCLUSIVE)
   wSliceNormalBttn = WIDGET_BUTTON(wSliceObliqBase1, VALUE='Normal', $
      UVALUE='wSliceNormalBttn', /NO_RELEASE)
   wSliceCenterBttn = WIDGET_BUTTON(wSliceObliqBase1, VALUE='Center', $
      UVALUE='wSliceCenterBttn', /NO_RELEASE)
   WIDGET_CONTROL, wSliceNormalBttn, /SET_BUTTON
   wSliceObliqGoBttn = WIDGET_BUTTON(wSliceObliqBase, VALUE='Display', $
                                     UVALUE='wSliceObliqGoBttn')


   ; Block base widgets.
   wBlockDraw = WIDGET_DRAW(wBlockBase, XSIZE=128, YSIZE=128, $
      RETAIN=2, UVALUE='wBlockDraw', /BUTTON_EVENTS)
   wBlockModeBase = WIDGET_BASE(wBlockBase, /ROW, /EXCLUSIVE, /FRAME, $
                       XPAD=1, YPAD=1, SPACE=1)
   wBlockAddBttn = WIDGET_BUTTON(wBlockModeBase, VALUE='Add', $
                      UVALUE='wBlockAddBttn', /NO_RELEASE)
   wBlockSubBttn = WIDGET_BUTTON(wBlockModeBase, VALUE='Subtract', $
                      UVALUE='wBlockSubBttn', /NO_RELEASE)
   WIDGET_CONTROL, wBlockAddBttn, /SET_BUTTON
   wBlockGoBttn = WIDGET_BUTTON(wBlockBase, VALUE='Display', $
                                UVALUE='wBlockGoBttn')


   ; Iso-surface base widgets.
   wSurfDrawBase = WIDGET_BASE(wSurfBase, /COLUMN, SPACE=1, $
                               XPAD=1, YPAD=1, /FRAME)
   wSurfLabel = WIDGET_LABEL(wSurfDrawBase, VALUE='Surface Threshold', $
                             /ALIGN_LEFT)
   wSurfDraw = WIDGET_DRAW(wSurfDrawBase, XSIZE=192, YSIZE=128, $
      RETAIN=2, UVALUE='wSurfDraw', /BUTTON_EVENTS)
   wSurfText = WIDGET_TEXT(wSurfDrawBase, XSIZE=8, YSIZE=1, /FRAME, $
                           UVALUE='wSurfText', /EDITABLE)
   wSurfSideBase = WIDGET_BASE(wSurfBase, /ROW, SPACE=1, /EXCLUSIVE, $
                               XPAD=1, YPAD=1, /FRAME)
   wSurfLowBttn = WIDGET_BUTTON(wSurfSideBase, VALUE='Low', $
                                UVALUE='wSurfLowBttn', /NO_RELEASE)
   wSurfHighBttn = WIDGET_BUTTON(wSurfSideBase, VALUE='High', $
                                 UVALUE='wSurfHighBttn', /NO_RELEASE)
   WIDGET_CONTROL, wSurfLowBttn, /SET_BUTTON
   wSurfShadeBase = WIDGET_BASE(wSurfBase, /COLUMN, $
                       SPACE=1, XPAD=1, YPAD=1, /FRAME)
   wSurfShadeLab = WIDGET_LABEL(wSurfShadeBase, VALUE='Shading:')
   sName = dName
   sName[0] = 'Light-source'
   wSurfShadeDrop = WIDGET_DROPLIST(wSurfShadeBase, VALUE=sName, $
      UVALUE='wSurfShadeDrop')
   if (N_ELEMENTS(sName) LE 1L) then WIDGET_CONTROL, wSurfShadeBase, $
      SENSITIVE=0
   wSurfGoBttn = WIDGET_BUTTON(wSurfBase, VALUE='Display', $
                                     UVALUE='wSurfGoBttn')


   ; Projection base widgets.
   wProjTypeBase = WIDGET_BASE(wProjectBase, /COLUMN, SPACE=1, $
                               XPAD=1, YPAD=1, /FRAME)
   wProjTypeLab = WIDGET_LABEL(wProjTypeBase, VALUE='Projection Type', $
                     /ALIGN_LEFT)
   wProjTypeBase1 = WIDGET_BASE(wProjTypeBase, /ROW, /EXCLUSIVE, SPACE=1, $
                               XPAD=1, YPAD=1)
   wProjMaxBttn = WIDGET_BUTTON(wProjTypeBase1, VALUE='Max', $
                                UVALUE='wProjMaxBttn', /NO_RELEASE)
   wProjAvgBttn = WIDGET_BUTTON(wProjTypeBase1, VALUE='Avg', $
                                UVALUE='wProjAvgBttn', /NO_RELEASE)
   WIDGET_CONTROL, wProjMaxBttn, /SET_BUTTON
   wProjResoBase = WIDGET_BASE(wProjectBase, /COLUMN, SPACE=1, $
                               XPAD=1, YPAD=1, /FRAME)
   wProjResoLab = WIDGET_LABEL(wProjResoBase, VALUE='Resolution', $
                     /ALIGN_LEFT)
   wProjResoBase1 = WIDGET_BASE(wProjResoBase, /ROW, /EXCLUSIVE, $
                                SPACE=1, XPAD=1, YPAD=1)
   wProjLowBttn = WIDGET_BUTTON(wProjResoBase1, VALUE='Low', $
                                UVALUE='wProjLowBttn', /NO_RELEASE)
   wProjMedBttn = WIDGET_BUTTON(wProjResoBase1, VALUE='Med', $
                                UVALUE='wProjMedBttn', /NO_RELEASE)
   wProjHighBttn = WIDGET_BUTTON(wProjResoBase1, VALUE='High', $
                                 UVALUE='wProjHighBttn', /NO_RELEASE)
   WIDGET_CONTROL, wProjMedBttn, /SET_BUTTON
   wProjDQSlid = WIDGET_SLIDER(wProjectBase, MIN=0, MAX=100, VALUE=100, $
                             UVALUE='wProjDQSlid', TITLE='Depth Queue %')
   wProjGoBttn = WIDGET_BUTTON(wProjectBase, VALUE='Display', $
                               UVALUE='wProjGoBttn')


   ; Profile base widgets.
   profWinY = 256
   wProfDraw = WIDGET_DRAW(wProfileBase, XSIZE=192, YSIZE=profWinY, $
      RETAIN=2, UVALUE='wProfDraw', /BUTTON_EVENTS)
   wProfTypeBase = WIDGET_BASE(wProfileBase, /COLUMN, /EXCLUSIVE, $
                               XPAD=1, YPAD=1, SPACE=1, /FRAME)
   wProfOrthoBttn = WIDGET_BUTTON(wProfTypeBase, Value='Orthogonal', $
      UVALUE='wProfOrthoBttn', /ALIGN_LEFT)
   wProfObliqBttn = WIDGET_BUTTON(wProfTypeBase, Value='Oblique', $
      UVALUE='wProfObliqBttn', /ALIGN_LEFT)
   WIDGET_CONTROL, wProfOrthoBttn, /SET_BUTTON


   ; Probe base widgets.
   wProbeXBase = WIDGET_BASE(wProbeBase, /ROW, XPAD=1, YPAD=1, SPACE=1)
   wProbeXLab = WIDGET_LABEL(wProbeXBase, VALUE='X')
   wProbeXText = WIDGET_TEXT(wProbeXBase, UVALUE='wProbeText', /EDITABLE, $
      VALUE=STRTRIM(STRING(FLOAT(szData[1]-1)/2.0)), XSIZE=12)
   wProbeYBase = WIDGET_BASE(wProbeBase, /ROW, XPAD=1, YPAD=1, SPACE=1)
   wProbeYLab = WIDGET_LABEL(wProbeYBase, VALUE='Y')
   wProbeYText = WIDGET_TEXT(wProbeYBase, UVALUE='wProbeText', /EDITABLE, $
      VALUE=STRTRIM(STRING(FLOAT(szData[2]-1)/2.0)), XSIZE=12)
   wProbeZBase = WIDGET_BASE(wProbeBase, /ROW, XPAD=1, YPAD=1, SPACE=1)
   wProbeZLab = WIDGET_LABEL(wProbeZBase, VALUE='Z')
   wProbeZText = WIDGET_TEXT(wProbeZBase, UVALUE='wProbeText', /EDITABLE, $
      VALUE=STRTRIM(STRING(FLOAT(szData[3]-1)/2.0)), XSIZE=12)


   ; View base widgets.
   ang1 = ( 30)
   ang2 = (-60)
   ang3 = (  0)
   wViewDrawBase = WIDGET_BASE(wViewBase, /ROW, SPACE=1, XPAD=1, YPAD=1)
   wViewDraw = WIDGET_DRAW(wViewDrawBase, XSIZE=128, YSIZE=128, $
      RETAIN=2, UVALUE='wViewDraw', /BUTTON_EVENTS)
   wViewDispBttn = WIDGET_BUTTON(wViewDrawBase, VALUE='Display', $
                      UVALUE='wViewDispBttn', /ALIGN_BOTTOM)

   wRotBase = WIDGET_BASE(wViewBase, /COLUMN, /FRAME, $
                 SPACE=1, XPAD=1, YPAD=1)
   wRotLab = WIDGET_LABEL(wRotBase, VALUE='Rotations:')

   wRot1Base = WIDGET_BASE(wRotBase, /ROW, SPACE=1, XPAD=1, YPAD=1)
   wRot1Slid = WIDGET_SLIDER(wRot1Base, Min=(-180), Max=(180), $
      VALUE=ang1, UVALUE='wRot1Slid', XSIZE=136, /DRAG)
   wRot1Drop = WIDGET_DROPLIST(wRot1Base, VALUE=['X','Y','Z'], $
                  UVALUE='wRot1Drop')
   WIDGET_CONTROL, wRot1Drop, SET_DROPLIST_SELECT=2

   wRot2Base = WIDGET_BASE(wRotBase, /ROW, SPACE=1, XPAD=1, YPAD=1)
   wRot2Slid = WIDGET_SLIDER(wRot2Base, Min=(-180), Max=(180), $
      VALUE=ang2, UVALUE='wRot2Slid', XSIZE=136, /DRAG)
   wRot2Drop = WIDGET_DROPLIST(wRot2Base, VALUE=['X','Y','Z'], $
                  UVALUE='wRot2Drop')
   WIDGET_CONTROL, wRot2Drop, SET_DROPLIST_SELECT=0

;   wRot3Base = WIDGET_BASE(wRotBase, /ROW, SPACE=1, XPAD=1, YPAD=1)
;   wid = WIDGET_LABEL(wRot3Base, VALUE='3')
;   wRot3Slid = WIDGET_SLIDER(wRot3Base, Min=(-180), Max=(180), $
;      VALUE=ang3, UVALUE='wRot3Slid', XSIZE=136, /DRAG)
;   wRot3Drop = WIDGET_DROPLIST(wRot3Base, VALUE=['X','Y','Z'], $
;                  UVALUE='wRot3Drop')
;   WIDGET_CONTROL, wRot3Drop, SET_DROPLIST_SELECT=0

   wZoomBase = WIDGET_BASE(wViewBase, /ROW, SPACE=1, XPAD=1, YPAD=1, $
                           /FRAME)
   wZoomSlid = WIDGET_SLIDER(wZoomBase, Min=50, Max=100, VALUE=50, $
                  XSIZE=136, UVALUE='wZoomSlid', /DRAG)
   wid = WIDGET_LABEL(wZoomBase, VALUE='Zoom %')

   wZFacBase = WIDGET_BASE(wViewBase, /ROW, SPACE=1, XPAD=1, YPAD=1, $
                           /FRAME)
   wZFacSlid = WIDGET_SLIDER(wZFacBase, Min=50, Max=200, VALUE=100, $
                  XSIZE=136, UVALUE='wZFacSlid', /DRAG)
   wid = WIDGET_LABEL(wZfacBase, VALUE='Z %')

;   wPerspBase = WIDGET_BASE(wViewBase, /ROW, SPACE=1, XPAD=1, YPAD=1, $
;                            /FRAME)
;   wPerspSlid = WIDGET_SLIDER(wPerspBase, Min=0, Max=10, VALUE=0, $
;                   XSIZE=136, UVALUE='wPerspSlid', /DRAG)
;   wid = WIDGET_LABEL(wPerspBase, VALUE='Persp.')


   ; Threshold base widgets.
   wThreshLabel = WIDGET_LABEL(wThreshBase, VALUE='Data Threshold', $
                               /ALIGN_LEFT)
   wThreshDraw = WIDGET_DRAW(wThreshBase, XSIZE=192, YSIZE=128, $
      RETAIN=2, UVALUE='wThreshDraw', /BUTTON_EVENTS)
   wThreshLowBase = WIDGET_BASE(wThreshBase, /ROW, SPACE=1, $
                                XPAD=1, YPAD=1)
   wThreshLowText = WIDGET_TEXT(wThreshLowBase, XSIZE=8, YSIZE=1, /FRAME, $
                                UVALUE='wThreshLowText', /EDITABLE)
   wThreshLowLabel = WIDGET_LABEL(wThreshLowBase, VALUE='Min')
   wThreshHighBase = WIDGET_BASE(wThreshBase, /ROW, SPACE=1, $
                                 XPAD=1, YPAD=1)
   wThreshHighText = WIDGET_TEXT(wThreshHighBase, XSIZE=8, YSIZE=1, /FRAME, $
                                UVALUE='wThreshHighText', /EDITABLE)
   wThreshHighLabel = WIDGET_LABEL(wThreshHighBase, VALUE='Max')
   wThreshTransBase = WIDGET_BASE(wThreshBase, /ROW, SPACE=1, $
                                  XPAD=1, YPAD=1)
   wThreshTransText = WIDGET_TEXT(wThreshTransBase, XSIZE=8, YSIZE=1, $
                         /FRAME, UVALUE='wThreshTransText', /EDITABLE)
   wThreshTransLabel = WIDGET_LABEL(wThreshTransBase, VALUE='Transp.')

   ; Set the color mode.
   DEVICE, GET_VISUAL_NAME=visualName
   IF (visualName EQ 'TrueColor' OR visualName EQ 'DirectColor') THEN $
       DEVICE, DECOMPOSED=0

   ; Create the pixmap used for profiling.
   WINDOW, /FREE, /PIXMAP, XSIZE=192, YSIZE=profWinY, RETAIN=2
   profPix = !D.Window

   ; Create the pixmap used for the surface histogram.
   WINDOW, /FREE, /PIXMAP, XSIZE=192, YSIZE=128, RETAIN=2
   surfPix = !D.Window

   ; Create the pixmap used for the threshold histogram.
   WINDOW, /FREE, /PIXMAP, XSIZE=192, YSIZE=128, RETAIN=2
   threshPix = !D.Window

   ; Create the pixmap used for dynamic screen updating.
   WINDOW, /FREE, /PIXMAP, XSIZE=winX, YSIZE=winY, RETAIN=2
   XYOUTS, 0, 0, '.', /DEVICE
   ERASE
   pixWin = !D.Window

   ; Initialize the Z buffer.
   screenDevice = !D.Name
   SET_PLOT, 'Z'
   DEVICE, /Z_BUFFERING, SET_RESOLUTION=[winX, winY]
   SET_PLOT, screenDevice

   if (detachBase and NOT runModal) then $
      WIDGET_CONTROL, wDrawBase, /REALIZE
   WIDGET_CONTROL, wMainBase, /REALIZE

   WIDGET_CONTROL, /HOURGLASS

   WIDGET_CONTROL, wMainDraw, GET_VALUE=mainWin
   WIDGET_CONTROL, wSliceDraw, GET_VALUE=sliceWin
   WIDGET_CONTROL, wBlockDraw, GET_VALUE=blockWin
   WIDGET_CONTROL, wSurfDraw, GET_VALUE=SurfWin
   WIDGET_CONTROL, wThreshDraw, GET_VALUE=threshWin
   WIDGET_CONTROL, wProfDraw, GET_VALUE=profWin
   WIDGET_CONTROL, wViewDraw, GET_VALUE=viewWin
   WSET, mainWin

   ; Set up the color state.
   sColorState = Viz3D_LoadColor(DIFFSHADE=20)

   ; View state.
   sViewState = Viz3D_View(viewWin, ANG1=ang1, ANG2=ang2, ANG3=ang3, $
      DIR1='Z', DIR2='X', DIR3='Y', ZOOM=0.75, XMAX=szData[1]-1L, $
      YMAX=szData[2]-1L, ZMAX=szData[3]-1L)
   WIDGET_CONTROL, wZoomSlid, SET_VALUE=ROUND(100.0 * sViewState.zoomFac)

   ; Threshold state.
   hMinData = PTR_NEW(minData)
   hMaxData = PTR_NEW(maxData)
   hLowThresh = PTR_NEW(minData)
   hHighThresh = PTR_NEW(maxData)
   hTransVal = PTR_NEW(minData)
   sThreshState = {SThreshState, $
                   histColor:7, lowColor:2, highColor:5, $
                   axisColor:3, transColor:6, $
                   wThreshDraw:wThreshDraw, $
                   wThreshLowText:wThreshLowText, $
                   wThreshHighText:wThreshHighText, $
                   wThreshTransText:wThreshTransText, $
                   threshWin:threshWin, hDataHist:hDataHist, $
                   threshPix:threshPix, $
                   hMinData:hMinData, hMaxData:hMaxData, $
                   hTransVal:hTransVal, $
                   hLowThresh:hLowThresh, $
                   hHighThresh:hHighThresh, $
                   moveType:0}
   WIDGET_CONTROL, wThreshLowText, $
      SET_VALUE=STRTRIM(STRING(minData[0]),2)
   WIDGET_CONTROL, wThreshHighText, $
      SET_VALUE=STRTRIM(STRING(maxData[0]),2)

   ; Slice state.
   sSliceState = $
      {SSliceState, sliceWin:sliceWin, $
       wSliceOrthoBase:wSliceOrthoBase, wSliceObliqBase:wSliceObliqBase, $
       wSliceXBttn:wSliceXBttn, wSliceYBttn:wSliceYBttn, $
       wSliceZBttn:wSliceZBttn, drawMode:0, planeMode:1, orthoDir:1, $
       orthoPos:0.5, obliqNormal:[0.0,0.0,1.0], obliqCenter:[0.5,0.5,0.5], $
       obliqMove:0, edgeColor:6, fillColor:1, normColor:5}

   ; Block state.
   sBlockState = $
      {SBlockState, blockWin:blockWin, $
       blockMode:1, c1:[szData[1]-1, szData[2]-1, szData[3]-1]/3, $
       c2:(2*[szData[1]-1, szData[2]-1, szData[3]-1])/3, $
       blockColor:6, c1Color:4, c2Color:1, frontBack:1}

   ; Iso-surface state.
   hSurfThresh = PTR_NEW((TEMPORARY(minData) + TEMPORARY(maxData))/2.0D)
   sSurfState = $
      {SSurfState, surfWin:surfWin, wSurfShadeBase:wSurfShadeBase, $
       surfPix:surfPix, wSurfShadeDrop:wSurfShadeDrop, curShade:0, $
       histColor:7, tColor:2, axisColor:3, hRangeHist:hRangeHist, $
       surfSide:0, hSurfThresh:hSurfThresh, wSurfText:wSurfText}

   ; Projection state.
   sProjState = $
      {SProjState, projType:0, projReso:1, depthQ:100}

   ; Profile state.
   sProfState = $
      {SProfState, profType:0, profDir:1, frontBack:1, onFace:1, $
       x1Ortho:FLOAT(szData[1]-1)/2.0, x2Ortho:FLOAT(szData[1]-1)/2.0, $
       y1Ortho:FLOAT(szData[2]-1)/2.0, y2Ortho:FLOAT(szData[2]-1)/2.0, $
       z1Ortho:0.0, z2Ortho:FLOAT(szData[3]-1), $
       x1Obliq:FLOAT(szData[1]-1)/2.0, x2Obliq:FLOAT(szData[1]-1)/2.0, $
       y1Obliq:FLOAT(szData[2]-1)/2.0, y2Obliq:FLOAT(szData[2]-1)/2.0, $
       z1Obliq:0.0, z2Obliq:FLOAT(szData[3]-1), $
       lineColor:6, axisColor:3, markColor1:4, markColor2:1, $
       profWin:profWin, profPix:profPix, profWinY:profWinY}

   ; Profile state.
   sProbeState = $
      {SProbeState, wProbeXText:wProbeXText, wProbeYText:wProbeYText, $
                    wProbeZText:wProbeZText, x:FLOAT(szData[1]-1)/2.0, $
                    y:FLOAT(szData[2]-1)/2.0, z:FLOAT(szData[3]-1)/2.0, $
                    lineColor:1}

   ; Main state.
   sMainState = {SVizState, curData:0, $
       szData:szData, detachBase:detachBase, winX:winX, winY:winY, $
       actMode:0, wMainBase:wMainBase, wDrawBase:wDrawBase, $
       wMainDraw:wMainDraw, wSliceBase:wSliceBase, wBlockBase:wBlockBase, $
       wSurfBase:wSurfBase, wThreshBase:wThreshBase, $
       wProjectBase:wProjectBase, wProfileBase:wProfileBase, $
       wProbeBase:wProbeBase, wViewBase:wViewBase, wCurMapBase:wSliceBase, $
       wStatText:wStatText, wSaveSubsetBttn:wSaveSubsetBttn, $
       wDeleteMenu:wDeleteMenu, wDataModeBase:wDataModeBase, $
       wDataDrop:wDataDrop, mainWin:mainWin, pixWin:pixWin, interpMode:1, $
       hMain:hMain, hDisplayList:hDisplayList, hDataList:hDataList, $
       hDataName:hDataName, cubeOn:1, axisOn:1, $
       cubeColor:3, axisColor:2, backColor:0, screenDevice:screenDevice, $
       cleanupView:0, cleanupBuffer:0, $
       hFrontDepth:PTR_NEW(Intarr(winX,winY)), $
       hBackDepth:PTR_NEW(Intarr(winX,winY)), $
       hFrontImage:PTR_NEW(Bytarr(winX,winY)), $
       hBackImage:PTR_NEW(Bytarr(winX,winY)), $
       sColorState:Temporary(sColorState), $
       sSliceState:Temporary(sSliceState), $
       sBlockState:Temporary(sBlockState), $
       sSurfState:Temporary(sSurfState), $
       sProjState:Temporary(sProjState), $
       sViewState:Temporary(sViewState), $
       sProfState:Temporary(sProfState), $
       sProbeState:Temporary(sProbeState), $
       sThreshState:Temporary(sThreshState) $
    }

   ; Fill the depth and image buffers.
   Viz3D_FillDepth, sMainState

   ; Draw the contents of the small windows.
   Viz3D_SliceShow, sMainState
   Viz3D_SurfShow, sMainState, /CALC_HIST
   Viz3D_ThreshShow, sMainState
   Viz3D_ViewShow, sMainState

   ; Draw cube in main window.
   SET_PLOT, 'Z'
   ERASE
   SET_PLOT, screenDevice
   Viz3D_DrawData, sMainState

   ; Put the main state in the top level base.
   WIDGET_CONTROL, wMainBase, SET_UVALUE=sMainState, /NO_COPY

   ; Turn off the hourglass cursor.
   event = WIDGET_EVENT(wMainBase, /NOWAIT)

   if (runModal) then begin

      ; Start event processing.

      XMANAGER, 'Slicer3', wMainBase, EVENT_HANDLER='Viz3D_Event', $
         CLEANUP='Viz3D_KillMain'


      ; All done, cleanup.

		IF WIDGET_INFO(dummyLeader, /VALID_ID) THEN $
			WIDGET_CONTROL, dummyLeader, /DESTROY

      ; Restore the system variables to their previous state.
      !Order = saveOrder
      !P = TEMPORARY(saveP)
      !X = TEMPORARY(saveX)
      !Y = TEMPORARY(saveY)
      !Z = TEMPORARY(saveZ)

      ; Restore the decomposed setting
      DEVICE, DECOMPOSED=saveDecomp

      ; Restore the previous color table.
      r = TEMPORARY(saveR)
      g = TEMPORARY(saveG)
      b = TEMPORARY(saveB)
      cur_red = TEMPORARY(save_curR)
      cur_green = TEMPORARY(save_curG)
      cur_blue = TEMPORARY(save_curB)

      ; Make the previous IDL window active.
      if (saveWin ge 0L) then WSET, saveWin

   endif else begin

      ; Start event processing.
      if (detachBase) then $
         XMANAGER, 'Slicer3', wDrawBase, /JUST_REG, $
            EVENT_HANDLER='Viz3D_SliceEvent', CLEANUP='Viz3D_KillDraw'

      XMANAGER, 'Slicer3', wMainBase, EVENT_HANDLER='Viz3D_Event', $
         CLEANUP='Viz3D_KillMain'

   endelse

end
;******************************************************************************

