//------------------------------------------------------------------------------
// (C) Copyright 2000
//     Theodore W. Hall
//     Department of Architecture
//     Chinese University of Hong Kong
//
// This software is provided "as is" and without any expressed or implied
// warranties, including, without limitation, the implied warranties of
// merchantability and fitness for any particular purpose.
//------------------------------------------------------------------------------
// PROGRAM:   SpinCalc
// PURPOSE:   An artificial-gravity calculator in JavaScript.
//
// USAGE:     See the <SCRIPT> and <FORM> elements in SpinCalc.htm
//
// DATE:      2000/01/19  T.W.H.  Original coding
//            2000/01/27  T.W.H.  Revisions to support Internet Explorer.
//            2000/06/02  T.W.H.  Revised "LED" design.
//            2002/08/28  T.W.H.  Revised init() function to allow re-init.
//            2003/05/05  T.W.H.  Added km and mi to r_factor
//------------------------------------------------------------------------------
// FUNCTION:  set_image
// PURPOSE:   To set the `src' of an existing image in the document.
//
// ARGS:      name   (string) Name of the image to set.
//            img    (Image) object from which to copy the `src' value.
//
// REMARKS:   This function implements a bug work-around for images in tables,
//            as documented in _JavaScript: The Definitive Guide_,
//            2nd Edition, O'Reilley & Associates.
//
// DATE:      2000/01/20  T.W.H.
//------------------------------------------------------------------------------
   function set_image (name, img)
   {
      var   dst ;

      dst = document[name] ;
      if (typeof (dst.length) == "number")
         dst[dst.length - 1].src = img.src ;
      else
         dst.src = img.src ;
   }

//------------------------------------------------------------------------------
// FUNCTION:  set_leds
// PURPOSE:   To set each of the colored "LED"s for the four parameters to
//            green, yellow, or red, according to their conformance with the
//            comfort criteria.
//
// DATE:      2000/01/24  T.W.H.
//            2000/01/27  T.W.H.
//            2000/06/02  T.W.H.  New LED design.
//------------------------------------------------------------------------------
   function set_leds ()
   {
      var   rpm, gf ;

      with (spindata)
      {

      // Radius.

         if (isNaN (r))
            set_image ("r_led", ledimg.none) ;
         else if (r <  4.)
            set_image ("r_led", ledimg.lo2) ;
         else if (r < 12.)
            set_image ("r_led", ledimg.lo1) ;
         else
            set_image ("r_led", ledimg.ok) ;

      // Angular velocity.  The comfort criteria are expressed in RPM.

         if (isNaN (w))
            set_image ("w_led", ledimg.none) ;
         else
         {
            rpm = w * 30. / Math.PI ;
            if      (rpm <= 2.)
               set_image ("w_led", ledimg.ok) ;
            else if (rpm <= 6.)
               set_image ("w_led", ledimg.hi1) ;
            else
               set_image ("w_led", ledimg.hi2) ;
         }

      // Tangential velocity.

         if (isNaN (v))
            set_image ("v_led", ledimg.none) ;
         else if (v <  6.)
            set_image ("v_led", ledimg.lo2) ;
         else if (v < 10.)
            set_image ("v_led", ledimg.lo1) ;
         else
            set_image ("v_led", ledimg.ok) ;

      // Centripetal acceleration.  The comfort criteria are expressed in
      // multiples of Earth gravity.  The upper green and yellow limits of
      // 1.1 g and 1.5 g are my own guesstimates.  I should allow a little
      // hypergravity before going yellow or red, but I don't really know how
      // much.

         if (isNaN (a))
            set_image ("a_led", ledimg.none)
         else
         {
            gf = a / g ;
            if      (gf < 0.1)
               set_image ("a_led", ledimg.lo2) ;
            else if (gf < 0.3)
               set_image ("a_led", ledimg.lo1) ;
            else if (gf < 1.1)
               set_image ("a_led", ledimg.ok) ;
            else if (gf < 1.5)
               set_image ("a_led", ledimg.hi1) ;
            else
               set_image ("a_led", ledimg.hi2) ;
         }
      }
   }

//------------------------------------------------------------------------------
// FUNCTION:  calc
//
// PURPOSE:   To compute radius, angular velocity, tangential velocity, and
//            centripetal acceleration.  These quantaties are interdependent.
//            Specifying any two determines the other two.
//
// ARGS:      what   (string) two characters for variables to calculate.
//
// DATE:      2000/01/19  T.W.H.
//            2000/01/27  T.W.H.
//------------------------------------------------------------------------------
   function calc (what)
   {
      with (spindata)
      {

      // JavaScript 1.2 supports `switch', but older versions don't.  So I
      // fall back to using a bunch of `if' statements.

         if ((what == "rw") || (what == "wr"))
         {
            r = v * v / a ;
            w = a / v ;
            r_text.value = r / r_factor[r_unit.selectedIndex] ;
            w_text.value = w / w_factor[w_unit.selectedIndex] ;
            set_image ("r_spec", specimg.r_av) ;
            set_image ("w_spec", specimg.w_av) ;
            return ;
         }
         if ((what == "rv") || (what == "vr"))
         {
            r = a / (w * w) ;
            v = a / w ;
            r_text.value = r / r_factor[r_unit.selectedIndex] ;
            v_text.value = v / v_factor[v_unit.selectedIndex] ;
            set_image ("r_spec", specimg.r_aw) ;
            set_image ("v_spec", specimg.v_aw) ;
            return ;
         }
         if ((what == "ar") || (what == "ra"))
         {
            a = v * w ;
            r = v / w ;
            a_text.value = a / a_factor[a_unit.selectedIndex] ;
            r_text.value = r / r_factor[r_unit.selectedIndex] ;
            set_image ("a_spec", specimg.a_vw) ;
            set_image ("r_spec", specimg.r_vw) ;
            return ;
         }
         if ((what == "vw") || (what == "wv"))
         {
            v = Math.sqrt (a * r) ;
            w = Math.sqrt (a / r) ;
            v_text.value = v / v_factor[v_unit.selectedIndex] ;
            w_text.value = w / w_factor[w_unit.selectedIndex] ;
            set_image ("v_spec", specimg.v_ar) ;
            set_image ("w_spec", specimg.w_ar) ;
            return ;
         }
         if ((what == "aw") || (what == "wa"))
         {
            a = v * v / r ;
            w = v / r ;
            a_text.value = a / a_factor[a_unit.selectedIndex] ;
            w_text.value = w / w_factor[w_unit.selectedIndex] ;
            set_image ("a_spec", specimg.a_rv) ;
            set_image ("w_spec", specimg.w_rv) ;
            return ;
         }
         if ((what == "av") || (what == "va"))
         {
            a = w * w * r ;
            v = w * r ;
            a_text.value = a / a_factor[a_unit.selectedIndex] ;
            v_text.value = v / v_factor[v_unit.selectedIndex] ;
            set_image ("a_spec", specimg.a_rw) ;
            set_image ("v_spec", specimg.v_rw) ;
            return ;
         }
      }
   }

//------------------------------------------------------------------------------
// FUNCTION:  adjust_prio
// PURPOSE:   To adjust parameter priorities.
//
// ARGS:      p   (string) 1-character highest-priority parameter.
//
// DATE:      2000/01/19  T.W.H.
//            2000/01/27  T.W.H.
//------------------------------------------------------------------------------
   function adjust_prio (p)
   {
      var   i, j ;

      with (spindata)
      {
         for (i = 0 ; i < 3 ; i++)
            if (prio[i] == p)
               break ;
         for (j = i ; j < 3 ; j++)
            prio[j] = prio[j+1] ;
         prio[3] = p ;
      }
   }

//------------------------------------------------------------------------------
// FUNCTION:  set_a
//            set_r
//            set_v
//            set_w
//
// PURPOSE:   onchange event handlers for <INPUT type="text"> form elements.
//
// DATE:      2000/01/19  T.W.H.
//            2000/01/27  T.W.H.
//------------------------------------------------------------------------------
   function set_a ()
   {
      with (spindata)
      {
         a = a_text.value * a_factor[a_unit.selectedIndex] ;
         set_image ("a_spec", specimg.input) ;
         adjust_prio ("a") ;
         calc (prio[0]+prio[1]) ;
         set_leds () ;
      }
   }

//------------------------------------------------------------------------------
   function set_r ()
   {
      with (spindata)
      {
         r = r_text.value * r_factor[r_unit.selectedIndex] ;
         set_image ("r_spec", specimg.input) ;
         adjust_prio ("r") ;
         calc (prio[0]+prio[1]) ;
         set_leds () ;
      }
   }

//------------------------------------------------------------------------------
   function set_v ()
   {
      with (spindata)
      {
         v = v_text.value * v_factor[v_unit.selectedIndex] ;
         set_image ("v_spec", specimg.input) ;
         adjust_prio ("v") ;
         calc (prio[0]+prio[1]) ;
         set_leds () ;
      }
   }

//------------------------------------------------------------------------------
   function set_w ()
   {
      with (spindata)
      {
         w = w_text.value * w_factor[w_unit.selectedIndex] ;
         set_image ("w_spec", specimg.input) ;
         adjust_prio ("w") ;
         calc (prio[0]+prio[1]) ;
         set_leds () ;
      }
   }

//------------------------------------------------------------------------------
// FUNCTION:  cvt_a
//            cvt_r
//            cvt_v
//            cvt_w
//
// PURPOSE:   onchange event handlers for <SELECT> form elements.
//
// DATE:      2000/01/19  T.W.H.
//            2000/01/27  T.W.H.
//------------------------------------------------------------------------------
   function cvt_a ()
   {
      with (spindata)
         a_text.value = a / a_factor[a_unit.selectedIndex] ;
   }

//------------------------------------------------------------------------------
   function cvt_r ()
   {
      with (spindata)
         r_text.value = r / r_factor[r_unit.selectedIndex] ;
   }

//------------------------------------------------------------------------------
   function cvt_v ()
   {
      with (spindata)
         v_text.value = v / v_factor[v_unit.selectedIndex] ;
   }

//------------------------------------------------------------------------------
   function cvt_w ()
   {
      with (spindata)
         w_text.value = w / w_factor[w_unit.selectedIndex] ;
   }

//------------------------------------------------------------------------------
// FUNCTION:  init
// PURPOSE:   To initialize the calculator.
//
// ARGS:      f   (Form) object reference.
//
// DATE:      2000/01/19  T.W.H.
//            2000/01/27  T.W.H.  Modify to satisfy restrictions in MSIE
//            2000/06/02  T.W.H.  New LED design
//            2002/08/28  T.W.H.  Allow re-initialization
//            2003/05/05  T.W.H.  Added km and mi to r_factor
//------------------------------------------------------------------------------
   function init (f)
   {
      var   i, opts ;

   // Netscape Navigator allows me to define my own new properties on the Form
   // object, but Microsoft Internet Explorer does not.  So, I must create my
   // own top-level object, and for convenience, copy fields from the Form.
   //
   // Create the object only once.

      if (typeof (spindata) != "object")
      {

      // Display a status message.

         defaultStatus = "Constructing.  Please wait ..." ;

      // Create the object.  Define Earth gravity in meters/second^2

         spindata   = new Object () ;
         spindata.g = 9.80665 ;

      // Load images for the LEDs and parameter specifications.  The latter
      // are images of mathematical formulas showing how the parameters were
      // calculated.

         spindata.ledimg  = new Object () ;
         spindata.specimg = new Object () ;
         with (spindata)
         {
            ledimg.none       = new Image () ;
            ledimg.lo2        = new Image () ;
            ledimg.lo1        = new Image () ;
            ledimg.ok         = new Image () ;
            ledimg.hi1        = new Image () ;
            ledimg.hi2        = new Image () ;
            specimg.none      = new Image () ;
            specimg.init      = new Image () ;
            specimg.input     = new Image () ;
            specimg.a_rv      = new Image () ;
            specimg.a_rw      = new Image () ;
            specimg.a_vw      = new Image () ;
            specimg.r_av      = new Image () ;
            specimg.r_aw      = new Image () ;
            specimg.r_vw      = new Image () ;
            specimg.v_ar      = new Image () ;
            specimg.v_aw      = new Image () ;
            specimg.v_rw      = new Image () ;
            specimg.w_ar      = new Image () ;
            specimg.w_av      = new Image () ;
            specimg.w_rv      = new Image () ;
            ledimg.none.src   = "led_none.gif" ;
            ledimg.lo2.src    = "led_lo2.gif" ;
            ledimg.lo1.src    = "led_lo1.gif" ;
            ledimg.ok.src     = "led_ok.gif" ;
            ledimg.hi1.src    = "led_hi1.gif" ;
            ledimg.hi2.src    = "led_hi2.gif" ;
            specimg.none.src  = "spec_none.gif" ;
            specimg.init.src  = "spec_init.gif" ;
            specimg.input.src = "spec_input.gif" ;
            specimg.a_rv.src  = "spec_a_rv.gif" ;
            specimg.a_rw.src  = "spec_a_rw.gif" ;
            specimg.a_vw.src  = "spec_a_vw.gif" ;
            specimg.r_av.src  = "spec_r_av.gif" ;
            specimg.r_aw.src  = "spec_r_aw.gif" ;
            specimg.r_vw.src  = "spec_r_vw.gif" ;
            specimg.v_ar.src  = "spec_v_ar.gif" ;
            specimg.v_aw.src  = "spec_v_aw.gif" ;
            specimg.v_rw.src  = "spec_v_rw.gif" ;
            specimg.w_ar.src  = "spec_w_ar.gif" ;
            specimg.w_av.src  = "spec_w_av.gif" ;
            specimg.w_rv.src  = "spec_w_rv.gif" ;
         }

      // Unit factors for radius, angular velocity, tangential velocity, and
      // acceleration.
      //
      // Internet Explorer doesn't allow me define new properties on the Form
      // object.  So, for convenience, I copy references from the Form to my
      // own object, and define parallel arrays of unit conversion factors.
      //
      // (In Navigator, I can define the factors as new properties of the Form
      // Select Option objects, without having to define a bunch of parallel
      // objects and arrays.  MSIE is not so friendly.)

         spindata.r_text   = f.r_text ;       // reference to `Input' element
         spindata.r_unit   = f.r_unit ;       // reference to `Select' element
         spindata.r_factor = new Array () ;   // parallel array of conversions
         spindata.w_text   = f.w_text ;       // reference to `Input' element
         spindata.w_unit   = f.w_unit ;       // reference to `Select' element
         spindata.w_factor = new Array () ;   // parallel array of conversions
         spindata.v_text   = f.v_text ;       // reference to `Input' element
         spindata.v_unit   = f.v_unit ;       // reference to `Select' element
         spindata.v_factor = new Array () ;   // parallel array of conversions
         spindata.a_text   = f.a_text ;       // reference to `Input' element
         spindata.a_unit   = f.a_unit ;       // reference to `Select' element
         spindata.a_factor = new Array () ;   // parallel array of conversions

      // Initialize the rotation parameters as new properties of `f':
      //    a   centripetal acceleration
      //    r   radius
      //    v   tangential velocity
      //    w   angular velocity
      //
      // The properties must be explicitly created before calling calc().

         spindata.a = 0. ;   // create the property
         spindata.r = 0. ;   // create the property
         spindata.v = 0. ;   // create the property
         spindata.w = 0. ;   // create the property

      // Parameter priorities.

         spindata.prio = new Array () ;
      }

   // Display a status message.

      defaultStatus = "Initializing.  Please wait ..." ;

   // Now that the object hierarchy is completely constructed, we can
   // initialize values (or reinitialize if the page is redrawn).

      with (spindata)
      {

      // Unit factors for radius, angular velocity, tangential velocity, and
      // acceleration.  All calculations are done in meters, radians, and
      // seconds.  Inputs are multiplied by the appropriate factors, and 
      // outputs are divided.
      //
      // It's essential that the <OPTION> `value' attribute and text content
      // agree with the value strings and in-line comments below.  In some
      // browsers, the JavaScript `Option.text' property is read-only, so I
      // can't set it here reliably; it must be set in the <OPTION> element.
      //
         opts = r_unit.options ;
         for (i = 0 ; i < opts.length ; i++)
         {
            if      (opts[i].value == "m")              // meters
               r_factor[i] = 1. ;
            else if (opts[i].value == "ft")             // feet
               r_factor[i] = 0.3048 ;
            else if (opts[i].value == "km")             // kilometers
               r_factor[i] = 1000. ;
            else if (opts[i].value == "mi")             // miles
               r_factor[i] = 0.3048 * 5280. ;
            else
            {
               alert (
                  "Unsupported: <OPTION value=\"" + opts[i].value + "\">") ;
               opts[i].value = "m" ;
               r_factor[i]   = 1. ;
            }
         }
         opts = w_unit.options ;
         for (i = 0 ; i < opts.length ; i++)
         {
            if      (opts[i].value == "rad")            // radians/second
               w_factor[i] = 1. ;
            else if (opts[i].value == "deg")            // degrees/second
               w_factor[i] = Math.PI / 180. ;
            else if (opts[i].value == "rpm")            // rotations/minute
               w_factor[i] = Math.PI / 30. ;
            else
            {
               alert (
                  "Unsupported: <OPTION value=\"" + opts[i].value + "\">") ;
               opts[i].value = "rad" ;
               w_factor[i]   = 1. ;
            }
         }
         opts = v_unit.options ;
         for (i = 0 ; i < opts.length ; i++)
         {
            if      (opts[i].value == "m")              // meters/second
               v_factor[i] = 1. ;
            else if (opts[i].value == "ft")             // feet/second
               v_factor[i] = 0.3048 ;
            else if (opts[i].value == "kph")            // kilometers/hour
               v_factor[i] = 1000. / 3600. ;
            else if (opts[i].value == "mph")            // miles/hour
               v_factor[i] = 0.3048 * 5280. / 3600. ;
            else
            {
               alert (
                  "Unsupported: <OPTION value=\"" + opts[i].value + "\">") ;
               opts[i].value = "m" ;
               v_factor[i]   = 1. ;
            }
         }
         opts = a_unit.options ;
         for (i = 0 ; i < opts.length ; i++)
         {
            if      (opts[i].value == "m")              // meters/second^2
               a_factor[i] = 1. ;
            else if (opts[i].value == "ft")             // feet/second^2
               a_factor[i] = 0.3048 ;
            else if (opts[i].value == "g")              // Earth gravity
               a_factor[i] = g ;
            else
            {
               alert (
                  "Unsupported: <OPTION value=\"" + opts[i].value + "\">") ;
               opts[i].value = "m" ;
               a_factor[i]   = 1. ;
            }
         }

      // I explicitly initialize two values, and calc() the other two.  The
      // calc() function sets the specification formulae for the two
      // calculated values.  I need to reset those to "initial value"; else, a
      // situation can occur in which three specifications display formulae
      // and only one displays "initial value" or "input".  This arises from
      // initial parameter priorities that don't reflect the order of this
      // initial calculation.

         a            = g ;
         w            = 1. ;
         a_text.value = a / a_factor[a_unit.selectedIndex] ;
         w_text.value = w / w_factor[w_unit.selectedIndex] ;

         calc ("rv") ;

         set_image ("r_spec", specimg.init) ;
         set_image ("w_spec", specimg.init) ;
         set_image ("v_spec", specimg.init) ;
         set_image ("a_spec", specimg.init) ;

         set_leds () ;

      // Set the initial parameter priorities.  These determine which
      // parameters to recompute whenever a parameter changes.  New input
      // receives the highest priority.  The two parameters with the lowest
      // priority (oldest input) are recomputed.

         prio[0] = "r" ;
         prio[1] = "w" ;
         prio[2] = "v" ;
         prio[3] = "a" ;
      }

   // Done initializing.

      defaultStatus = "Ready."
   }

