WaBis

walter.bislins.ch

JavaScript: Simulation of the Eearths Curvature

JavaScript for the earth curvature simulator on page Finding the curvature of the Earth.

JavaScript für den Erd-Krümmungs-Simulator der Seite Wie stark ist die Krümmung der Erde?.

#INCLUDE JsGraphX3D.inc
#INCLUDE ControlPanel.inc
#INCLUDE Tabs.inc

<jscript>

function EarthModel() {

  this.DemoText = '';
  this.Description = '';

  this.rEarth = 6371000;
  this.rFEarth = this.rEarth * Math.PI / 2;
  this.nLines = 45;
  this.showModel = 1; // 1 -> globe, 2 -> flat, 3 -> both
  this.showGrid = 1; // 0 -> none, 1 -> globe, 3 -> projection of globe to flat
  this.showFlatHorizon = false;
  this.showTangent = false;
  this.showEyeLevel = true;
  this.deviceRatio = 3 / 2; // width / height of device screen
  this.sceneWidth = 0;
  this.sceneHeight = 0;
  this.viewcenterHorizon = 0;  // 0 -> globe, 1 -> flat earth, 2 -> between, 3 -> eye level

  this.HeightSlider = 0;
  this.HeightSliderLast = 0;
  this.Height = 100;

  this.ViewAngle = 60;  // viewAngle in deg
  this.ViewAngleField = 60;
  this.ViewAngleSlider = 0;
  this.ViewAngleSliderLast = 0;
  this.Roll = 0;
  this.Nick = 0;
  this.Pan  = 0;
  this.FocalLength = 0;
  this.FocalLengthField = 0;
  this.FocalLengthSlider = 0;
  this.FocalLengthSliderLast = 0;

  this.rDisk = 0;          // d
  this.dHorizon = 0;       // s
  this.zDisk = 0;          // p
  this.hDisk = 0;          // R - b
  this.hDip = 0;           // b
  this.aDip = 0;           // alpha
  this.aEarth = 0;         // 180 - 2 * alpha
  this.dView = 0;          // v
  this.aDelta = 0;         // aDip / nLines
  this.posEarth = [ 0, 0, -this.rEarth ];
  this.camViewAngle = 0.1; // rad
  this.camPos = [ 0, 0, 0 ];
  this.camUp = [ 0, 0, 1 ];
  this.camViewCenter = [ 0, 1, 0 ];
  this.camSceneSize = 1;

  this.ObjType = 3;     // 0 = Bedford, 1 = scale, 2 = ship, 3 = T-Tower, 4 = city, 5 = mountain, 6 = bridge
  this.ObjSideType = 0; // 0 = lin, 1 = 2col, 2 = rand, 3 = cos, 4 = sin
  this.ObjSizeType = 0; // 0 = lin, 1 = 2col, 2 = rand, 3 = cos, 4 = sin

  this.ObjDist = 50;
  this.Slider_ObjDist_Log = 0;
  this.Slider_ObjDist_Log_Last = 0;

  this.ObjSidePos = 100;
  this.Slider_ObjSidePos_Log = 0;
  this.Slider_ObjSidePos_Log_Last = 0;

  this.ObjSize = 65;
  this.Slider_ObjSize_Log = 0;
  this.Slider_ObjSize_Log_Last = 0;

  this.ObjDeltaDist = 300;
  this.Slider_ObjDeltaDist_Log = 0;
  this.Slider_ObjDeltaDist_Log_Last = 0;

  this.ObjSideVar = 0;
  this.Slider_ObjSideVar_Log = 0;
  this.Slider_ObjSideVar_Log_Last = 0;

  this.ObjSizeVar = 0;
  this.NObjects = 0;

  this.Update();
}

EarthModel.prototype.Update = function() {
  var pi180 = Math.PI;
  var pi90 = pi180 / 2;
  var pi45 = pi180 / 4;
  var toRad  = pi180 / 180;

  if ( this.rEarth < 100000 ) this.rEarth = 100000;

  // handle height changes
  if ( this.HeightSliderLast != this.HeightSlider ) {
    this.Height = Math.pow( 10, 0 + 9 * this.HeightSlider );
  }
  if ( this.Height < 0.1 ) this.Height = 0.1;
  if ( this.Height > 1000000000 ) this.Height = 1000000000;
  this.HeightSlider = ( Math.log10( this.Height ) - 0 ) / 9;
  this.HeightSliderLast = this.HeightSlider;

  // handle ViewAngle and FocalLength changes
  if ( this.FocalLengthSlider != this.FocalLengthSliderLast ) {
    var f = (2000-21) * Math.pow(this.FocalLengthSlider,2) + 21;  // f = 21..2000
    this.ViewAngle = 2 * Math.atan( 43.2 / 2 / f ) / toRad;
  } else if ( this.FocalLengthField != this.FocalLength ) {
    this.ViewAngle = 2 * Math.atan( 43.2 / 2 / this.FocalLengthField ) / toRad;
  } else if ( this.ViewAngleSlider != this.ViewAngleSliderLast ) {
    this.ViewAngle = this.ViewAngleSlider;
  } else if ( this.ViewAngleField != this.ViewAngle ) {
    this.ViewAngle = this.ViewAngleField;
  }
  if ( this.ViewAngle < 0.1 ) this.ViewAngle = 0.1;
  if ( this.ViewAngle > 160 ) this.ViewAngle = 160;
  this.camViewAngle = this.ViewAngle * toRad;
  this.FocalLength = 43.2 / ( 2 * Math.tan( this.camViewAngle / 2 ) );
  this.FocalLengthField = this.FocalLength;
  var f = this.FocalLength - 21;
  if (f < 0) f = 0;
  this.FocalLengthSlider = Math.pow(f/(2000-21),1/2);
  this.FocalLengthSliderLast = this.FocalLengthSlider;
  this.ViewAngleField = this.ViewAngle;
  this.ViewAngleSlider = this.ViewAngle;
  this.ViewAngleSliderLast = this.ViewAngle;

  // object settings
  this.NObjects = Math.round(this.NObjects);
  if (this.NObjects < 0) this.NObjects = 0;
  if (this.NObjects > 500) this.NObjects = 500;
  
  if (this.Slider_ObjDist_Log_Last != this.Slider_ObjDist_Log) {
    var distSign = this.Slider_ObjDist_Log < 0 ? -1 : 1;
    var distVal = Math.abs( this.Slider_ObjDist_Log );
    if (distVal < 1) {
      this.ObjDist = 100 * distVal;
    } else {
      this.ObjDist = 10 * Math.pow( 10, this.Slider_ObjDist_Log );
    }
    this.ObjDist *= distSign;
  }
  var distLimit = this.rEarth * Math.PI / 4;
  if (this.ObjDist < -distLimit) this.ObjDist = -distLimit;
  if (this.ObjDist >  distLimit) this.ObjDist =  distLimit;
  var distSign = this.ObjDist < 0 ? -1 : 1;
  var distVal  = Math.abs( this.ObjDist );
  if (distVal < 100) {
    this.Slider_ObjDist_Log = distVal / 100;
  } else {
    this.Slider_ObjDist_Log = Math.log10( distVal / 10 );
  }
  this.Slider_ObjDist_Log *= distSign;
  this.Slider_ObjDist_Log_Last = this.Slider_ObjDist_Log;

  if (this.Slider_ObjSidePos_Log != this.Slider_ObjSidePos_Log_Last) {
    var sidePosSign = this.Slider_ObjSidePos_Log < 0 ? -1 : 1;
    var sidePosVal = Math.abs( this.Slider_ObjSidePos_Log );
    if (sidePosVal < 1) {
      this.ObjSidePos = 100 * sidePosVal;
    } else {
      this.ObjSidePos = 10 * Math.pow( 10, sidePosVal );
    }
    this.ObjSidePos *= sidePosSign;
  }
  var sidePosLimit = this.rEarth * Math.PI / 4;
  if (this.ObjSidePos < -sidePosLimit) this.ObjSidePos = -sidePosLimit;
  if (this.ObjSidePos > sidePosLimit) this.ObjSidePos = sidePosLimit;
  var sidePosSign = this.ObjSidePos < 0 ? -1 : 1;
  var sidePosVal = Math.abs( this.ObjSidePos );
  if (sidePosVal < 100) {
    this.Slider_ObjSidePos_Log = sidePosVal / 100;
  } else {
    this.Slider_ObjSidePos_Log = Math.log10( sidePosVal / 10 );
  }
  this.Slider_ObjSidePos_Log *= sidePosSign;
  this.Slider_ObjSidePos_Log_Last = this.Slider_ObjSidePos_Log;
  
  if (this.Slider_ObjSideVar_Log != this.Slider_ObjSideVar_Log_Last) {
    var sideVarSign = this.Slider_ObjSideVar_Log < 0 ? -1 : 1;
    var sideVarVal = Math.abs( this.Slider_ObjSideVar_Log );
    if (sideVarVal < 1) {
      this.ObjSideVar = 10 * sideVarVal;
    } else {
      this.ObjSideVar = Math.pow( 10, sideVarVal );
    }
    this.ObjSideVar *= sideVarSign;
  }
  var sideVarLimit = this.rEarth * Math.PI / 4;
  if (this.ObjSideVar < -sideVarLimit) this.ObjSideVar = -sideVarLimit;
  if (this.ObjSideVar > sideVarLimit) this.ObjSideVar = sideVarLimit;
  var sideVarSign = this.ObjSideVar < 0 ? -1 : 1;
  var sideVarVal = Math.abs( this.ObjSideVar );
  if (sideVarVal < 10) {
    this.Slider_ObjSideVar_Log = sideVarVal / 10;
  } else {
    this.Slider_ObjSideVar_Log = Math.log10( sideVarVal );
  }
  this.Slider_ObjSideVar_Log *= sideVarSign;
  this.Slider_ObjSideVar_Log_Last = this.Slider_ObjSideVar_Log;

  if (this.Slider_ObjSize_Log != this.Slider_ObjSize_Log_Last) {
    this.ObjSize = Math.pow( 10, this.Slider_ObjSize_Log );
  }
  if (this.ObjSize < 0.001) this.ObjSize = 0.001;
  if (this.ObjSize > 1e9) this.ObjSize = 1e9;
  this.Slider_ObjSize_Log = Math.log10( this.ObjSize );
  this.Slider_ObjSize_Log_Last = this.Slider_ObjSize_Log;

  if (this.Slider_ObjDeltaDist_Log != this.Slider_ObjDeltaDist_Log_Last) {
    this.ObjDeltaDist = 10 * Math.pow( 10, this.Slider_ObjDeltaDist_Log );
  }
  var deltaDistLimit = this.rEarth * Math.PI / 2;
  if (this.ObjDeltaDist < 0.001) this.ObjDeltaDist = 0.001;
  if (this.ObjDeltaDist > deltaDistLimit) this.ObjDeltaDist = deltaDistLimit;
  this.Slider_ObjDeltaDist_Log = Math.log10( this.ObjDeltaDist / 10 );
  this.Slider_ObjDeltaDist_Log_Last = this.Slider_ObjDeltaDist_Log;

  // compute diverse values
  this.aDip = Math.acos( this.rEarth / (this.rEarth + this.Height) );
  this.aEarth = pi180 - 2 * this.aDip;
  this.rDisk = this.rEarth * Math.sin( this.aDip );
  this.dHorizon = this.rEarth * this.aDip;
  this.hDisk = this.rEarth * Math.cos( this.aDip );
  this.hDip = this.rEarth - this.hDisk;
  this.dView = ( this.Height + this.rEarth ) * Math.sin( this.aDip );
  this.aDelta = this.aDip / this.nLines;
  this.dDelta = this.showModel & 1 ? this.aDelta * this.rEarth : 0;
  this.posEarth = [ 0, 0, -(this.rEarth + this.Height) ];
  this.zDisk = (this.rEarth + this.Height) - ( this.rEarth * Math.cos( this.aDip ) );

  // compute camViewCenter from panning
  if ( Math.abs( this.Nick ) < 30 / 30 ) this.Nick = 0;
  if ( Math.abs( this.Roll ) < 30 / 30 ) this.Roll = 0;
  if (this.Nick < -85) this.Nick = -85;
  if (this.Nick >  45) this.Nick =  45;
  if (this.Roll < -85) this.Roll = -45;
  if (this.Roll >  85) this.Roll = 85;
  if (this.Pan  >  90) this.Pan  =  90;
  if (this.Pan  < -90) this.Pan  = -90;
  var dvc, avc, nickRad;
  if (this.Nick < 0) {
    nickRad = - Math.pow( this.Nick/85, 2 ) * 85 * toRad;
  } else {
    nickRad = Math.pow( this.Nick/45, 2 ) * 45 * toRad;
  }
  dvc = Math.sqrt( this.rDisk * this.rDisk + this.zDisk * this.zDisk );
  if (this.viewcenterHorizon == 0) {
    // view center is globe horizon
    avc = this.aDip;
  } else if (this.viewcenterHorizon == 1) {
    // view center is flat earth equator
    avc = Math.atan( this.Height / this.rFEarth );
  } else if (this.viewcenterHorizon == 2) {
    // view center is between globe horizon and flat earth equator
    avc = (Math.atan( this.Height / this.rFEarth ) + this.aDip) / 2;
  } else {
    // view center is eye level
    avc = 0;
  }
  avc -= nickRad;
  apan = this.Pan * toRad;
  if ( avc > 0.9999*pi90 ) avc = 0.9999*pi90;
  if ( avc < -0.9999*pi90 ) avc = -0.9999*pi90;
  var zvc = - dvc * Math.sin( avc );
  var rvc = dvc * Math.cos( avc );
  var xvc = rvc * Math.sin( apan );
  var yvc = rvc * Math.cos( apan );
  this.camViewCenter = [ xvc, yvc, zvc ];
  var nvc = JsgVect3.Norm( this.camViewCenter );
  var v = JsgVect3.Norm( [ xvc, yvc, 0 ] );
  var n = JsgVect3.Mult( v, [ 0, 0, 1 ] );
  var up = JsgVect3.Mult( n, nvc );

  // compute camera up and pos
  var a = this.Roll * toRad;
  this.camUp = JsgVect3.Add( JsgVect3.Scale( n, Math.sin(a) ), JsgVect3.Scale( up, Math.cos(a) ) );
  this.camPos = [ 0, 0, 0 ];

  // compute scene size taking device ratio into account
  var vpRatio = 3 / 2;
  var diag = 2 * this.dView * Math.tan( this.camViewAngle / 2 );
  this.sceneHeight = diag / Math.sqrt( 1 + this.deviceRatio*this.deviceRatio );
  this.sceneWidth = this.deviceRatio * this.sceneHeight;
  if ( this.deviceRatio > vpRatio ) {
    // device is wider then viewport
    this.camSceneSize = this.sceneWidth / vpRatio;
  } else {
    this.camSceneSize = this.sceneHeight;
  }

}

var Model = new EarthModel();
var UpdateAllRunning = false;

function UpdateAll( stopAnimation ) {
  if (UpdateAllRunning) return;
  UpdateAllRunning = true;
  try {
    stopAnimation = xDefBool( stopAnimation, true );
    if (stopAnimation) StopAnimation();
    Model.Update();
    ControlPanels.Update();
    graph.Redraw();
  }
  finally {
    UpdateAllRunning = false;
  }
}

var graph = NewGraphX3D( {
  Id: 'JsGraph1',
  Width: '100%',
  Height: '66.67%',
  DrawFunc: DrawModel,
  OnClick: function(){ StopAnimation(); },
  AutoReset: true,
  AutoClear: true,
  AutoScalePix: true
} );

function DrawModel( g ) {

  var m = Model;
  var toRad  = Math.PI / 180;
  g.MaxCurveSegments = 256;
  g.SetAngleMeasure( 'rad' );
  g.SetViewport( 0, 1, -0.5, -2 );
  g.SetGraphClipping( true, '', 0 );
  g.SetCameraClipping( 0.001 );
  g.SetWindowToCameraScreen();

  g.SetCamera( {
    SceneSize: m.camSceneSize,
    CamPos: m.camPos,
    CamUp: m.camUp,
    CamViewCenter: m.camViewCenter,
  } );
  g.SetCameraZoom( 1 );

  // var specialLineWidth = m.NObjects == 0 ? 2 : 1;
  var specialLineWidth = 1.5;

  // draw eye level
  var rotz = JsgMat3.RotatingZ( -m.Pan * toRad );
  if ( m.showEyeLevel ) {
    g.SetLineAttr( 'magenta', 1 );
    g.SetAlpha( 1 );
    g.Line3D( JsgMat3.Trans( rotz, [ -m.sceneWidth, m.rDisk, 0 ] ), JsgMat3.Trans( rotz, [ m.sceneWidth, m.rDisk, 0 ] ) );
    g.SetTextAttr( 'Arial', 12, 'magenta', 'normal', 'normal', 'center', 'bottom', 6 );
    g.SetTextRotation( -m.Roll*Math.PI/180 );
    g.Text3D( 'Eye-Level', JsgMat3.Trans( rotz, [ 0, m.rDisk, 0 ] ) );
    g.SetTextRotation( 0 );
  }

  // Flat Earth

  if ( m.showModel & 2 ) {
  
    // set clipping if both models are shown to restrict graphic to one half of the screen
    if (m.showModel & 1) {
      var xpos = 0;
      if (m.ObjSidePos < 0) xpos = g.CanvasWidth / 2;
      g.SetClipRect( xpos, 0, g.CanvasWidth/2, g.CanvasHeight, 'canvas' );
    }

    // draw flat earth horizon and equator
    g.SetAlpha( 1 );
    g.SetPlane( [ 0, 0, -m.Height ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
    g.SetLineAttr( 'black', 1 );
    g.CircleOnPlane( 0, 0, m.rFEarth, 1 );
    g.SetLineWidth( 2 );
    g.CircleOnPlane( 0, 0, 2*m.rFEarth, 1 );

    var aMax = 2 * Math.PI;
    var alpha = 0.6 - 0.5 * (Math.log10( m.Height ) / 9);
    g.SetAlpha( alpha );
    g.SetLineAttr( 'black', 1 );
    
    if (m.showGrid > 0) {

      // circle lines
      var crDelta = m.rFEarth / 12;
      var crMax = 2 * m.rFEarth - crDelta / 2;
      g.SetPlane( [ 0, 0, -m.Height ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
      for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
        g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
      }
      if (m.Height < 700000) {
        crMax = crDelta;
        crDelta /= 10;
        crMax -= crDelta / 2;
        for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
          g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
        }
      }
      if (m.Height < 30000) {
        crMax = crDelta;
        crDelta /= 10;
        crMax -= crDelta / 2;
        for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
          g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
        }
      }
      if (m.Height < 3000) {
        crMax = crDelta;
        crDelta /= 10;
        crMax -= crDelta / 2;
        for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
          g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
        }
      }
      if (m.Height < 300) {
        crMax = crDelta;
        crDelta /= 10;
        crMax -= crDelta / 2;
        for ( var cr = crDelta; cr < crMax; cr += crDelta ) {
          g.ArcOnPlane( 0, 0, cr, 0, aMax, 1 );
        }
      }

      // ray lines
      g.SetAlpha( alpha );
      g.SetLineAttr( 'black', 1 );
      var caDelta = Math.PI / 12;
      var caMax = 2 * Math.PI;
      caMax -= caDelta / 2;
      var r = 2 * m.rFEarth;
      for ( var ca = 0; ca < caMax; ca += caDelta ) {
        var c = Math.cos( ca );
        var s = Math.sin( ca );
        g.LineOnPlane( r * c, r * s, 0, 0 );
      }
    }

    DrawObjectsBeforeHorizon( g, false );
  
    // reset clipping
    if (m.showModel & 1) {
      g.SetClipping( 'canvas' );
    }

  } // end nLines > 0

  // Globe Earth

  if ( m.showModel & 1 ) {

    // set clipping if both models are shown to restrict graphic to one half of the screen
    if (m.showModel & 2) {
      var xpos = g.CanvasWidth / 2;
      if (m.ObjSidePos < 0) xpos = 0;
      g.SetClipRect( xpos, 0, g.CanvasWidth/2, g.CanvasHeight, 'canvas' );
    }

    DrawObjectsBehindHorizon( g );

    var alpha = 0.6 - 0.5 * (Math.log10( m.Height ) / 9);
    g.SetAlpha( alpha );
    g.SetLineAttr( 'blue', 1 );

    // show globe grid
    if ( m.showGrid & 1 ) {

      // latitude lines
      var latMax = m.aDip;
      var latStart = -( Math.floor( latMax / m.aDelta ) * m.aDelta );
      for ( var lat = latStart; lat < latMax; lat += m.aDelta ) {
        var dLatPlaneDisk = m.hDisk / Math.cos( lat );
        var longMax = Math.acos( dLatPlaneDisk / m.rEarth );
        var longStart = -( Math.floor( longMax / m.aDelta ) * m.aDelta );
        g.NewPoly3D();
        for ( var long = longStart; long < longMax; long += m.aDelta ) {
          g.AddPointToPoly3D( PointOnEarth( lat, long ) );
        }
        g.AddPointToPoly3D( PointOnEarth( lat, longMax ) );
        g.DrawPoly3D( 1 );
      }

      // longitude lines
      var longMax = m.aDip;
      var longStart = -( Math.floor( longMax / m.aDelta ) * m.aDelta );
      for ( var long = longStart; long < longMax; long += m.aDelta ) {
        var rLong = m.rEarth * Math.cos( long );
        var latMax = Math.acos( m.hDisk / rLong );
        var latStart = -( Math.floor( latMax / m.aDelta ) * m.aDelta );
        g.NewPoly3D();
        g.AddPointToPoly3D( PointOnEarth( -latMax, long ) );
        for ( var lat = latStart; lat < latMax; lat += m.aDelta ) {
          g.AddPointToPoly3D( PointOnEarth( lat, long ) );
        }
        g.AddPointToPoly3D( PointOnEarth( latMax, long ) );
        g.DrawPoly3D( 1 );
      }

    } // end show globe grid

    g.SetLineAttr( 'red', 1 );

    // show flat grid
    if ( m.showGrid & 2 ) {

      // latitude lines on flat model
      var latMax = m.aDip;
      var latStart = -( Math.floor( latMax / m.aDelta ) * m.aDelta );
      for ( var lat = latStart; lat < latMax; lat += m.aDelta ) {
        var dLatPlaneDisk = m.hDisk / Math.cos( lat );
        var longMax = Math.acos( dLatPlaneDisk / m.rEarth );
        var longStart = -( Math.floor( longMax / m.aDelta ) * m.aDelta );
        g.NewPoly3D();
        for ( var long = longStart; long < longMax; long += m.aDelta ) {
          g.AddPointToPoly3D( PointOnPlane( lat, long ) );
        }
        g.AddPointToPoly3D( PointOnPlane( lat, longMax ) );
        g.DrawPoly3D( 1 );
      }

      // longitude lines on flat model
      var longMax = m.aDip;
      var longStart = -( Math.floor( longMax / m.aDelta ) * m.aDelta );
      for ( var long = longStart; long < longMax; long += m.aDelta ) {
        var rLong = m.rEarth * Math.cos( long );
        var latMax = Math.acos( m.hDisk / rLong );
        var latStart = -( Math.floor( latMax / m.aDelta ) * m.aDelta );
        g.NewPoly3D();
        g.AddPointToPoly3D( PointOnPlane( -latMax, long ) );
        for ( var lat = latStart; lat < latMax; lat += m.aDelta ) {
          g.AddPointToPoly3D( PointOnPlane( lat, long ) );
        }
        g.AddPointToPoly3D( PointOnPlane( latMax, long ) );
        g.DrawPoly3D( 1 );
      }

    } // end flat grid

    if ( (m.showGrid & 2) || m.showFlatHorizon ) {

      // horizon on flat model
      g.SetPlane( [ 0, 0, -m.Height ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
      g.SetAlpha( 1 );
      g.SetLineAttr( 'red', specialLineWidth );
      g.CircleOnPlane( 0, 0, m.rDisk, 1 );

    }

    // Globe Horizon
    g.SetAlpha( 1 );
    g.SetLineAttr( 'blue', specialLineWidth );
    g.SetPlane( [ 0, 0, -m.zDisk ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
    g.CircleOnPlane( 0, 0, m.rDisk, 1 );

    // show tangent line to globe horizon
    if ( m.showTangent ) {
      g.SetLineAttr( 'black', 1 );
      g.Line3D( JsgMat3.Trans( rotz, [ -m.sceneWidth, m.rDisk, -m.zDisk ] ), JsgMat3.Trans( rotz, [ m.sceneWidth, m.rDisk, -m.zDisk ] ) );
    }

    // draw all objects in the foreground
    DrawObjectsBeforeHorizon( g, true );

    // reset clipping
    if (m.showModel & 2) {
      g.SetClipping( 'canvas' );
    }

  } // end model globe

  // draw frame
  g.SetAlpha( 1 );
  g.SetLineAttr( 'black', 2 );
  var xDir = JsgVect3.Mult( g.Camera.ViewDir, g.Camera.CamUp );
  var yDir = JsgVect3.Mult( g.Camera.ViewDir, xDir );
  g.SetPlane( g.Camera.CamViewCenter, xDir, yDir, true );
  g.RectOnPlane( -m.sceneWidth/2, -m.sceneHeight/2, m.sceneWidth/2, m.sceneHeight/2, 1 );

  // label split screen
  if (m.showModel == 3) {
    var oldTrans = g.SelectTrans( 'viewport' );
    // label left
    var txt = 'Flat Earth';
    if (m.ObjSidePos < 0) {
      txt = 'Globe';
    }
    g.SetTextAttr( 'Arial', 16, 'black', 'bold', 'normal', 'left', 'top', 10 );
    g.Text( txt, 0, 0 );
    // label right
    var txt = 'Globe';
    if (m.ObjSidePos < 0) {
      txt = 'Flat Earth';
    }
    g.SetTextAttr( 'Arial', 16, 'black', 'bold', 'normal', 'right', 'top', 10 );
    g.Text( txt, g.VpInnerWidth, 0 );
    g.SelectTrans( oldTrans );
  }
  
  // draw demo text and description
  if (m.DemoText != '') {
    var oldTrans = g.SelectTrans( 'viewport' );

    g.SetTextAttr( 'Arial', 20, 'black', 'normal', 'normal', 'center', 'top', 10 );
    g.Text( m.DemoText, g.VpInnerWidth/2, 0 );

    if (m.Description != '') {
      g.SetTextAttr( 'Arial', 16, 'black', 'normal', 'normal', 'center', 'top', 10 );
      g.Text( m.Description, g.VpInnerWidth/2, 30 );
    }
    
    g.SelectTrans( oldTrans );
  }

}

function DrawObjectsBehindHorizon( g ) {
  var m = Model;

  // init some loop variables used in DrawObjectsBeforeHorizon too
  if (m.NObjects == 0) return;
  m.aObjDelta = m.ObjDeltaDist / m.rEarth;
  m.aObjFirst = m.ObjDist / m.rEarth;
  m.aObjLast = (m.NObjects-1) * m.aObjDelta + m.aObjFirst;
  m.MaxNObjectsToDraw = m.NObjects;
  m.NObjectsDrawn = 0;
  if (m.aObjLast > Math.PI) {
    m.MaxNObjectsToDraw = Math.floor( Math.PI / m.aObjDelta );
    m.aObjLast = (m.MaxNObjectsToDraw-1) * m.aObjDelta + m.aObjFirst;
    m.NObjectsDrawn = m.NObjects - m.MaxNObjectsToDraw;
  }
  m.aObjCurr = m.aObjLast;

  // if only objects before horizon exist skip this function
  var aSide = m.ObjSidePos / m.rEarth;
  var sideOnDisk = (m.rEarth - m.hDip) * Math.tan( aSide );
  var aClip = 0;
  if (m.rDisk > Math.abs(sideOnDisk)) {
    // not all objects are behind the horizon, so find the first object that is behind
    var distOnDisk = Math.sqrt( m.rDisk * m.rDisk - sideOnDisk * sideOnDisk );
    aClip = Math.asin( distOnDisk / m.rEarth );
  }
  if (m.aObjCurr <= aClip) return;
  m.isHiddenObj = true;

  // draw all objects behind horizon only and clip then at horizon
  g.SetAlpha(1);
  g.SetAreaAttr( '#ffa', '#666', 1 );
  g.SaveTrans3D();
  var aLimit = m.aObjFirst - m.aObjDelta/2;
  while (m.aObjCurr > aClip && m.aObjCurr > aLimit ) {
    DrawShapeVariants( g, m, true );
    m.NObjectsDrawn++;
    m.aObjCurr -= m.aObjDelta;
  }
  g.RestoreTrans3D();

  // clip at horizon
  if (m.NObjectsDrawn > 0) {
    g.SetPlane( [0,0,-m.zDisk], [1,0,0], [0,1,0] );
    g.SetBgColor( 'white' );
    g.CircleOnPlane( 0, 0, m.rDisk, 2 );
  }
}

function DrawObjectsBeforeHorizon( g, bOnGlobe ) {
  var m = Model;
  if (m.NObjects == 0) return;

  // Init parameters for flat earth here, because DrawObjectsBehindHorizon is not caller in that case
  if (!bOnGlobe) {
    m.aObjDelta = m.ObjDeltaDist / m.rEarth;
    m.aObjFirst = m.ObjDist / m.rEarth;
    m.aObjLast = (m.NObjects-1) * m.aObjDelta + m.aObjFirst;
    m.MaxNObjectsToDraw = m.NObjects;
    m.aObjCurr = m.aObjLast;
    m.NObjectsDrawn = 0;
  }

  var aLimit = m.aObjFirst - m.aObjDelta/2;
  if (m.aObjCurr < aLimit) return;
  m.isHiddenObj = false;

  g.SetAlpha(1);
  g.SetAreaAttr( 'yellow', 'black', 1 );
  g.SaveTrans3D();
  while (m.aObjCurr >= aLimit) {
    DrawShapeVariants( g, m, bOnGlobe );
    m.aObjCurr -= m.aObjDelta;
    m.NObjectsDrawn++;
  }
  g.RestoreTrans3D();
}

function SetTrans( g, m, lng, lat, size, alt, bOnGlobe ) {
  var d = 0;
  var s = 0;
  var xs = bOnGlobe ? m.ObjSize : -m.ObjSize;
  g.ResetTrans3D();
  g.TransScale3D( xs, m.ObjSize, m.ObjSize );
  g.TransMove3D( 0, 0, m.rEarth+alt );
  if (bOnGlobe) {
    g.TransRotateY3D( lat );
    g.TransRotateX3D( -lng );
  } else {
    d = lng * m.rEarth;
    s = -lat * m.rEarth;
  }
  g.TransMove3D( s, d, -(m.rEarth+m.Height) );
}

function DrawShapeVariants( g, m, bOnGlobe ) {

  // size: 0 -> lin, 1 -> alt, 2 -> rand, 3 -> cos, 4 -> sin

  var size = 1;
  if (m.ObjSizeType == 0) {
    // lin
    if (m.MaxNObjectsToDraw > 1) {
      size = 1 + m.ObjSizeVar * (2 / (m.MaxNObjectsToDraw - 1) * (m.MaxNObjectsToDraw-1 - m.NObjectsDrawn) - 1);
    }

  } else if (m.ObjSizeType == 1) {
    // alt
    size = 1 + m.ObjSizeVar * Math.cos( Math.PI * m.NObjectsDrawn );

  } else if (m.ObjSizeType == 2) {
    // rand
    size = 1 + m.ObjSizeVar * ( (64894678.9798467 * Math.sqrt(m.NObjectsDrawn)) % 2 - 1 );

  } else if (m.ObjSizeType == 3) {
    // cos
    if (m.MaxNObjectsToDraw > 1) {
      size = 1 - m.ObjSizeVar * Math.cos( 2 * Math.PI * m.NObjectsDrawn/(m.MaxNObjectsToDraw-1) );
    } else {
      size = 1 + m.ObjSizeVar;
    }

  } else if (m.ObjSizeType == 4) {
    // sin
    if (m.MaxNObjectsToDraw > 1) {
      size = 1 - m.ObjSizeVar * Math.sin( 2 * Math.PI * m.NObjectsDrawn/(m.MaxNObjectsToDraw-1) );
    } else {
      size = 1;
    }
  }

  // make size varying from 0..1
  size = (size + 1 - Math.abs(m.ObjSizeVar)) / 2;
  if (size < 0) size *= -1;

  var sizeHor = 1;
  if (m.ObjType == 0 || m.ObjType == 1 || m.ObjType == 3) {
    // Bedford, M-Rod, T-Tower: scale width proportionaly
    sizeHor = size;

  } else if (m.ObjType == 2) {
    // ship: scale width inverse proportional to size within some limits
    sizeHor = (1 - 0.75) * size + 0.75;
    size = (1 - 0.5) * size + 0.5;

  } else if (m.ObjType == 4) {
    // city: scale only height

  } else if (m.ObjType == 6) {
    // bridge: scale only height
  }

  var sideVar = m.ObjSideVar / m.rEarth / 2;
  var side = m.ObjSidePos / m.rEarth;
  if (m.ObjSideType == 0) {
    // lin
    if (m.MaxNObjectsToDraw > 1) {
      sideVar *= (2 / (m.MaxNObjectsToDraw - 1) * (m.MaxNObjectsToDraw-1 - m.NObjectsDrawn) - 1);
    }

    SetTrans( g, m, m.aObjCurr, side+sideVar, m.ObjSize, 0, bOnGlobe );
    g.SetPlane( [0,0,0], [sizeHor,0,0], [0,0,size] );
    DrawShape( g, m );

  } else if (m.ObjSideType == 1) {
    // 2col
    var pos1 = side - sideVar;
    var pos2 = side + sideVar;
    if (Math.abs(pos1) < Math.abs(pos2)) {
      // invert drawing sequence
      var tmp = pos1;
      pos1 = pos2;
      pos2 = tmp;
    }
    // assert: pos1 is farther to the side than pos2 -> |pos1| >= |pos2|
    m.Col = 1;
    g.SetPlane( [0,0,0], [sizeHor,0,0], [0,0,size] );
    SetTrans( g, m, m.aObjCurr, pos1, m.ObjSize, 0, bOnGlobe );
    DrawShape( g, m );
    m.Col = 2;
    SetTrans( g, m, m.aObjCurr, pos2, m.ObjSize, 0, bOnGlobe );
    g.SetPlane( [0,0,0], [sizeHor,0,0], [0,0,size] );
    DrawShape( g, m );

  } else if (m.ObjSideType == 2) {
    // rand
    sideVar *= (186573.6498496 * Math.sqrt(m.NObjectsDrawn) % 2) - 1;
    g.SetPlane( [0,0,0], [sizeHor,0,0], [0,0,size] );
    SetTrans( g, m, m.aObjCurr, side+sideVar, m.ObjSize, 0, bOnGlobe );
    DrawShape( g, m );

  } else if (m.ObjSideType == 3) {
    // cos
    if (m.MaxNObjectsToDraw > 1) {
      sideVar *= Math.cos( 2 * Math.PI * m.NObjectsDrawn/(m.MaxNObjectsToDraw-1) );
    }
    g.SetPlane( [side,0,0], [sizeHor,0,0], [0,0,size] );
    SetTrans( g, m, m.aObjCurr, side+sideVar, m.ObjSize, 0, bOnGlobe );
    DrawShape( g, m );

  } else if (m.ObjSideType == 4) {
    // sin
    if (m.MaxNObjectsToDraw > 1) {
      sideVar *= Math.sin( 2 * Math.PI * m.NObjectsDrawn/(m.MaxNObjectsToDraw-1) );
    } else {
      sideVar = 0;
    }
    g.SetPlane( [side,0,0], [sizeHor,0,0], [0,0,size] );
    SetTrans( g, m, m.aObjCurr, side+sideVar, m.ObjSize, 0, bOnGlobe );
    DrawShape( g, m );

  }
}

function DrawShape( g, m ) {
  // plane and transformations are set
  
  function setBgColor( normalColor, hiddenColor ) {
    if (m.isHiddenObj) {
      g.SetBgColor( hiddenColor );
    } else {
      g.SetBgColor( normalColor );
    }
  }

  // ObjType: 0 = Bedford, 1 = M-Rod, 2 = ship, 3 = T-Tower, 4 = city, 5 = mountain, 6 = bridge

  if (m.ObjType == 0) {
    // Bedford
    if (m.NObjectsDrawn == 0) {

      // draw bridge with bar
      setBgColor( '#aaa', '#ccc' );
      g.OpenPath3D();
      g.ArcOnPlane( 0, 0, -0.8, Math.PI, 0 );
      g.PolygonOnPlane( [ 0.8, 1.2, 1.2, -1.2, -1.2, -0.8 ], [ 0, 0, 1.1, 1.1, 0., 0 ] );
      g.Path3D( 3 );
      setBgColor( 'white', 'white' );
      g.RectOnPlane( -0.3, 0.85, 0.3, 1.15, 3 );
      setBgColor( 'black', '#444' );
      g.RectOnPlane( -0.3, 0.95, 0.3, 1.05, 3 );
      
    } else if (m.NObjects > 2 && m.NObjectsDrawn == m.NObjects-1) {

      // draw scope
      g.SetAlpha( 0.3 );
      setBgColor( 'black', 'black' );
      g.RectOnPlane( -0.01, 0, 0.01, 0.955, 3 );
      g.OpenPath3D();
      g.CircleOnPlane( 0, 1, 0.05 );
      g.CircleOnPlane( 0, 1, -0.04 );
      g.Path3D( 3 );
      g.SetAlpha( 1 );
      g.SetColor( 'orange' );
      g.LineOnPlane( -0.04, 1, 0.04, 1 );
      g.LineOnPlane( -0.02, 1-0.02, -0.005, 1-0.005 );
      g.LineOnPlane(  0.02, 1-0.02,  0.005, 1-0.005 );
      g.LineOnPlane( -0.02, 1+0.02, -0.005, 1+0.005 );
      g.LineOnPlane(  0.02, 1+0.02,  0.005, 1+0.005 );

    } else {
    
      // draw rods
      setBgColor( 'yellow', '#ff4' );
      g.RectOnPlane( -0.025, 0, 0.025, 1, 3 );
      setBgColor( 'red', '#f44' );
      g.CircleOnPlane( 0, 1, 0.06, 3 );
      //g.CircleOnPlane( 0, 0.698, 0.06, 3 );
      
    }

  } else if (m.ObjType == 1) {
    // M-Rod
    setBgColor( 'yellow', '#ff4' );
    g.RectOnPlane( -0.1, 0, 0.1, 1, 2 );
    setBgColor( 'red', '#f66' );
    g.RectOnPlane( -0.1, 0.5, 0, 1, 2 );
    for (var i = 0.1; i < 1; i += 0.2 ) {
      g.RectOnPlane( 0, i, 0.1, i+0.1, 2 );
    }
    g.RectOnPlane( -0.1, 0, 0.1, 1, 1 );

  } else if (m.ObjType == 2) {
    // ship
    setBgColor( '#654130', '#937162' );
    g.PolygonOnPlane( [-0.15, 0.15, 0.2, -0.2, -0.15], [0, 0, 0.15, 0.15, 0], 3 );
    g.LineOnPlane( 0, 0, 0, 1.05 );
    setBgColor( '#7b899a', '#a9b6c6' );
    g.PolygonOnPlane( [-0.2, -0.1, 0.1, 0.2, 0.3, -0.3, -0.2], [0.15, 0.2, 0.2, 0.15, 0.4, 0.4, 0.15], 3 );
    setBgColor( '#b5bdc3', '#d4d9dd' );
    g.PolygonOnPlane( [-0.3, -0.1, 0.1, 0.3, 0.25, -0.25, -0.3], [0.4, 0.45, 0.45, 0.4, 0.6, 0.6, 0.4], 3 );
    setBgColor( '#dfe0df', '#eff0ef' );
    g.PolygonOnPlane( [-0.25, -0.1, 0.1, 0.25, 0.2, -0.2, -0.25], [0.6, 0.65, 0.65, 0.6, 0.8, 0.8, 0.6], 3 );
    g.PolygonOnPlane( [-0.2, -0.05, 0.05, 0.2, 0.1, -0.1, -0.2], [0.8, 0.85, 0.85, 0.8, 1, 1, 0.8], 3 );

  } else if (m.ObjType == 3) {
    // T-Tower
    g.PolygonOnPlane( [ 0.09, -0.12, -0.06, 0.14, 0.12, 0.12, 0.16, 0.16, 0.14, 0.06, -0.06, -0.14, -0.12, -0.12, -0.16, -0.16, -0.14, 0.06, 0.12, -0.09 ], [0.31, 0.14, 0.48, 0.82, 0.89, 0.93, 1, 0.89, 0.82, 0.48, 0.48, 0.82, 0.89, 0.93, 1, 0.89, 0.82, 0.48, 0.14, 0.31 ], 1 );
    g.PolygonOnPlane( [ 0.06, -0.09, 0.09, -0.06 ], [ 0.48, 0.31, 0.31, 0.48 ], 1 );
    g.PolygonOnPlane( [ -0.28, 0.28, 0.16, -0.16, -0.28 ], [ 0.89, 0.89, 0.93, 0.93, 0.89 ], 1 );
    setBgColor( '#a38d60', '#dcc493' );
    g.PolygonOnPlane( [ -0.145, -0.095, -0.095, 0.095, 0.095, 0.145, 0.145, -0.145, -0.145 ], [ 0, 0, 0.105, 0.105, 0, 0, 0.137, 0.137, 0 ], 3 );

  } else if (m.ObjType == 4) {
    // city
    var mi = m.NObjectsDrawn % 3;
    var mr = m.NObjectsDrawn % 6;
    if (mr <= 2) {
      // mirror x axes
      g.Plane.XDir[0] *= -1;
    }
    if (mi == 0) {
      setBgColor( '#eca992', '#ecc3b4' );
      g.RectOnPlane( -1.5, 0, -1.2, 0.4, 3 );
      setBgColor( '#ffeacc', '#fff4e5' );
      g.RectOnPlane( -1.45, 0.4, -1.25, 0.7, 3 );
      g.RectOnPlane( -1.4, 0.7, -1.3, 0.75, 3 );
      g.LineOnPlane( -1.5, 0.3, -1.2, 0.3 );
      g.LineOnPlane( -1.45, 0.6, -1.25, 0.6 );

      setBgColor( '#586a78', '#7d868d' );
      g.RectOnPlane( -0.7, 0, -0.4, 0.5, 3 );
      g.RectOnPlane( -0.65, 0.5, -0.45, 0.8, 3 );
      g.RectOnPlane( -0.6, 0, -0.5, 1, 3 );
      g.LineOnPlane( -0.56, 1, -0.56, 1.2 );
      g.LineOnPlane( -0.53, 1, -0.53, 1.1 );

      setBgColor( '#f4caa9', '#f4e3d5' );
      g.PolygonOnPlane( [ -0.05, -0.05, 0, 0, 0.1, 0.2, 0.2, 0.25, 0.25, -0.05 ], [ 0, 0.45, 0.5, 0.6, 0.75, 0.6, 0.5, 0.45, 0, 0 ], 3 );
      g.LineOnPlane( 0, 0, 0, 0.5 );
      g.LineOnPlane( 0.2, 0, 0.2, 0.5 );

      setBgColor( '#ec7266', '#f3b0a9' );
      g.RectOnPlane( 0.4, 0, 0.6, 0.9, 3 );

      setBgColor( '#586a78', '#7d868d' );
      g.PolygonOnPlane( [ 1.1, 1.2, 1.35, 1.45, 1.1 ], [ 0, 0.8, 0.8, 0, 0 ], 3 );
      g.LineOnPlane( 1.4, 0, 1.3, 0.8 );
      g.LineOnPlane( 1.25, 0.8, 1.25, 1 );
      g.LineOnPlane( 1.3, 0.8, 1.3, 1 );

    } else if (mi == 1) {
      setBgColor( '#fbd9c6', '#fbebe2' );
      g.RectOnPlane( -1.7, 0, -1.5, 0.5, 3 );
      setBgColor( '#fbebe2', '#fbf5f1' );
      g.PolygonOnPlane( [ -1.68, -1.68, -1.6, -1.52, -1.52, -1.68 ], [ 0.5, 0.6, 0.7, 0.6, 0.5, 0.5 ], 3 );
      g.LineOnPlane( -1.68, 0.6, -1.52, 0.6 );

      setBgColor( '#6d6870', '#9a939e' );
      g.RectOnPlane( -1.1, 0, -0.9, 0.55, 3 );
      setBgColor( '#dfbb97', '#dfcfbf' );
      g.RectOnPlane( -1, 0, -0.8, 0.45, 3 );
      g.RectOnPlane( -1.05, 0.55, -0.95, 0.6, 3 );

      setBgColor( '#b7997c', '#cdbdad' );
      g.RectOnPlane( -0.3, 0, 0.2, 0.4, 3 );
      setBgColor( '#ffeac6', '#fff5e5' );
      g.RectOnPlane( -0.25, 0.3, 0.15, 0.35, 3 );

      setBgColor( '#f3dca7', '#f3e5c5' );
      g.RectOnPlane( 0.6, 0, 1, 0.4, 3 );
      setBgColor( '#c19f89', '#e2bfa9' );
      g.PolygonOnPlane( [ 0.7, 0.7, 0.8, 0.9, 0.9, 0.7 ], [ 0, 0.5, 0.7, 0.5, 0, 0 ], 3 );

      setBgColor( '#4b5866', '#7b8591' );
      g.RectOnPlane( 1.6, 0, 1.9, 0.6, 3 );
      setBgColor( '#012a46', '#3b5465' );
      g.RectOnPlane( 1.7, 0, 1.8, 0.65, 3 );

    } else {
      setBgColor( '#eacfae', '#eaddce' );
      g.RectOnPlane( -2, 0, -1.8, 0.4, 3 );
      setBgColor( '#525462', '#8a8c98' );
      g.RectOnPlane( -1.8, 0, -1.7, 0.4, 3 );

      setBgColor( '#b59776', '#dac2a8' );
      g.RectOnPlane( -1.4, 0, -1, 0.3, 3 );
      setBgColor( '#9c504d', '#c18d8b' );
      g.RectOnPlane( -1.35, 0.3, -1.05, 0.35, 3 );

      setBgColor( '#46352d', '#827067' );
      g.RectOnPlane( -0.7, 0, -0.5, 0.3, 3 );
      setBgColor( '#e5c9a5', '#efe2d1' );
      g.PolygonOnPlane( [ -0.7, -0.6, -0.5 ], [ 0.2, 0.3, 0.2 ], 1 );

      setBgColor( '#f5d9b4', '#f5e9d9' );
      g.RectOnPlane( -0.3, 0, 0.1, 0.2, 3 );

      setBgColor( '#39596b', '#738c9a' );
      g.PolygonOnPlane( [ 0.3, 0.4, 0.5, 0.4, 0.3 ], [ 0.4, 0.6, 0.4, 0.2, 0.4 ], 3 );
      setBgColor( '#878b8e', '#a9aeb1' );
      g.PolygonOnPlane( [ 0.3, 0.3, 0.4, 0.5, 0.5, 0.3 ], [ 0, 0.4, 0.2, 0.4, 0, 0 ], 3 );
      g.LineOnPlane( 0.4, 0, 0.4, 0.2 );

      setBgColor( '#f5d9b4', '#f5e9d9' );
      g.RectOnPlane( 0.6, 0, 0.8, 0.2, 3 );

      setBgColor( '#dccace', '#f0dde1' );
      g.RectOnPlane( 0.9, 0, 1.2, 0.5, 3 );
      setBgColor( '#d3b79e', '#e3d7cc' );
      g.RectOnPlane( 1, 0, 1.15, 0.6, 3 );

      setBgColor( '#a17268', '#c7a8a2' );
      g.PolygonOnPlane( [ 1.4, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2, 2, 1.4 ], [ 0, 0.15, 0.2, 0.15, 0.2, 0.15, 0.2, 0.15, 0, 0 ], 3 );
      g.LineOnPlane( 1.6, 0, 1.6, 0.15 );
      g.LineOnPlane( 1.8, 0, 1.8, 0.15 );

    }

  } else if (m.ObjType == 5) {
    // mountain
    var mi = m.NObjectsDrawn % 3;
    var mr = m.NObjectsDrawn % 6;
    if (mr > 2) {
      // mirror x axes
      g.Plane.XDir[0] *= -1;
    }
    if (mi == 0) {
      setBgColor( '#dbe2ed', '#e9f0fc' );
      g.PolygonOnPlane( [ -1.3, -0.8, -0.6, -0.3, -0.1, 0.1, 0.2, 0.4, 0.5, 0.6, 1.3, -1.3 ], [ 0, 0.6, 0.6, 1, 0.9, 0.7, 0.7, 0.9, 0.8, 0.85, 0, 0 ], 3 );
      g.PolygonOnPlane( [ -0.3, 0, 0.3, 0.5, 0.8 ], [ 0.1, 0.2, 0.5, 0.5, 0.4 ], 1 );
      g.PolygonOnPlane( [ -0.6, -0.3, 0.1 ], [ 0.6, 0.4, 0.3 ], 1 );
      g.PolygonOnPlane( [ -0.2, -0.1, 0.1 ], [ 0.5, 0.6, 0.7 ], 1 );
      g.PolygonOnPlane( [ 0.1, 0.2, 0.4, 0.5 ], [ 0.5, 0.6, 0.7, 0.8 ], 1 );

    } else if (mi == 1) {
      setBgColor( '#92aace', '#c4d2e8' );
      g.PolygonOnPlane( [ -1.3, -0.8, -0.7, -0.2, 0.1, 0.2, 0.5, 0.6, 0.7, 1.3, -1.3 ], [ 0, 0.5, 0.5, 1, 0.8, 0.7, 0.7, 0.6, 0.6, 0, 0 ], 3 );
      g.PolygonOnPlane( [ -0.7, -0.6, -0.3 ], [ 0.5, 0.5, 0.2 ], 1 );
      g.LineOnPlane( 0.2, 0.7, 0.7, 0.2 );
      g.LineOnPlane( 0.6, 0.6, 1.1, 0.1 );

    } else {
      setBgColor( '#abacb1', '#c8c9cf' );
      g.PolygonOnPlane( [ -1.3, -0.7, -0.6, -0.3, -0.3, -0.2, -0.1, 0.1, 0.3, 0.5, 0.6, 0.8, 1, 1.1, 1.3, -1.3 ],  [ 0, 0.6, 0.6, 0.8, 0.9, 1, 1, 0.8, 0.5, 0.4, 0.5, 0.5, 0.4, 0.2, 0, 0 ], 3 );
      g.PolygonOnPlane( [ -1, -0.6, -0.4, -0.2, 0 ], [ 0.1, 0.4, 0.5, 0.5, 0.3 ], 1 );
      g.PolygonOnPlane( [ -0.6, -0.5, -0.4 ], [ 0.6, 0.6, 0.5 ], 1 );
      g.PolygonOnPlane( [ -0.2, -0.1, 0 ], [ 1, 0.8, 0.4 ], 1 );
      g.PolygonOnPlane( [ 0, 0.3, 0.6, 0.8, 0.9 ], [ 0.1, 0.2, 0.5, 0.4, 0.3 ], 1 );
    }

  } else if (m.ObjType == 6) {
    // bridge

    // draw road except on SideVarType = rand
    var p1 = JsgVect3.Null();
    var p2 = JsgVect3.Null();
    g.GetTransPointOnPlane( -1, 1, p1 );
    g.GetTransPointOnPlane(  1, 1, p2 );
    if (m.NObjectsDrawn > 0 && m.ObjSideType != 2) {
      g.SaveTrans3D( true );
      g.NewPoly3D();
      if (m.Col == 1) {
        g.AddPointToPoly3D( m.LastRoadP11 );
        g.AddPointToPoly3D( m.LastRoadP12 );
      } else {
        g.AddPointToPoly3D( m.LastRoadP21 );
        g.AddPointToPoly3D( m.LastRoadP22 );
      }
      g.AddPointToPoly3D( p2 );
      g.AddPointToPoly3D( p1 );
      setBgColor( '#cdcac3', '#e8e4dc' );
      g.DrawPoly3D( 7 );
      g.RestoreTrans3D();
    }
    if (m.Col == 1) {
      m.LastRoadP11 = JsgVect3.Copy( p1 );
      m.LastRoadP12 = JsgVect3.Copy( p2 );
    } else {
      m.LastRoadP21 = JsgVect3.Copy( p1 );
      m.LastRoadP22 = JsgVect3.Copy( p2 );
    }

    // draw pilows
    setBgColor( '#adadab', '#d2d2cf' );
    g.RectOnPlane( -0.8, 0, -0.55, 0.55, 3 );
    g.RectOnPlane( 0.55, 0, 0.8, 0.55, 3 );
    setBgColor( '#8c8678', '#b9b19e' );
    g.RectOnPlane( -1, 0.55, 1, 0.7, 3 );
    setBgColor( '#565a56', '#858b85' );
    g.PolygonOnPlane( [ -1, -1, -0.95, -0.95, 0.95, 0.95, 1, 1, -1 ], [ 1, 0.9, 0.9, 0.7, 0.7, 0.9, 0.9, 1, 1, 1 ], 3 );

  }
}

function PointOnEarth( lat, long ) {
  var x = Model.rEarth * Math.sin( long );
  var rr = Model.rEarth * Math.cos( long );
  var y = rr * Math.sin( lat );
  var z = rr * Math.cos( lat );
  return [ x, y, z - (Model.rEarth + Model.Height) ];
}

function PointOnPlane( lat, long ) {
  var x = Model.rEarth * Math.sin( long );
  var rr = Model.rEarth * Math.cos( long );
  var y = rr * Math.sin( lat );
  return [ x, y, -Model.Height ];
}

</jscript>

{{TabSelectorsTop| CurveSettingsTabs | MarginTop }}
{{TabSel| Views }}
{{TabSel| Objects }}
{{TabSel| Units-Calc }}
{{TabLabel| Demos: }}
{{TabSelButton| Curve | CurveButton }}
{{TabSelButton| Causeway | CausewayButton }}
{{TabSelButton| TrnsmLine | TransLineButton }}
{{TabSelButton| Chicago | ChicagoButton }}
{{TabSelButton| Bedford | BedfordButton }}
{{EndTabSelectors}}

{{TabBoxes| CurveSettingsTabs }}

<jscript>

// install click handler für Demo button of Tabs
xOnDomReady( 
  function CB_OnDomReady_InstallTabButtonClickHandler() {
    var boxTabName = 'CurveSettingsTabs';
    Tabs.AddButtonClickHandler( boxTabName, 'CurveButton',
      function CB_OnClick_CurveTabButton( buttonData ) {
        StartAnimation( CurveAnimation );
      }
    );
    Tabs.AddButtonClickHandler( boxTabName, 'CausewayButton',
      function CB_OnClick_CurveTabButton( buttonData ) {
        StartAnimation( CausewayAnimation );
      }
    );
    Tabs.AddButtonClickHandler( boxTabName, 'TransLineButton',
      function CB_OnClick_TransLineTabButton( buttonData ) {
        StartAnimation( TransLineAnimation );
      }
    );
    Tabs.AddButtonClickHandler( boxTabName, 'BedfordButton',
      function CB_OnClick_BedfordTabButton( buttonData ) {
        StartAnimation( BedfordAnimation );
      }
    );
    Tabs.AddButtonClickHandler( boxTabName, 'ChicagoButton',
      function CB_OnClick_ChicagoTabButton( buttonData ) {
        StartAnimation( CityAnimation );
      }
    );
  }
);

ControlPanels.NewSliderPanel( {
  ModelRef: 'Model',
  OnModelChange: UpdateAll,
  Format: 'std',
  Digits: 3,
  ReadOnly: false,
  PanelFormat: 'InputMediumWidth'

} ).AddValueSliderField( {
  Name: 'Height',
  ValueRef: 'Height',
  SliderValueRef: 'HeightSlider',
  Mult: 1000,
  Units: 'km',
  Color: 'blue',
  Min: 0,
  Max: 1

} ).AddValueSliderField( {
  Name: 'ViewAngle',
  Label: 'View&ang;',
  ValueRef: 'ViewAngleField',
  SliderValueRef: 'ViewAngleSlider',
  Units: '&deg;',
  Color: 'black',
  Min: 1.24,
  Max: 91.6

} ).AddValueSliderField( {
  Name: 'FocalLength',
  ValueRef: 'FocalLengthField',
  SliderValueRef: 'FocalLengthSlider',
  Label: 'Zoom f',
  Units: 'mm',
  Color: 'black',
  Min: 0,
  Max: 1

} ).AddValueSliderField( {
  Name: 'Pan',
  Format: 'std',
  Digits: 3,
  Units: '&deg;',
  Color: 'green',
  Min: -90,
  Max: 90

} ).Render();


ControlPanels.NewSliderPanel( {
  ModelRef: 'Model',
  OnModelChange: UpdateAll,
  NCols: 2,
  Format: 'fix0',
  Digits: 0,
  ReadOnly: false,
  PanelFormat: 'InputMediumWidth'

} ).AddValueSliderField( {
  Name: 'Nick',
  Format: 'std',
  Digits: 3,
  Units: '&deg;',
  Color: 'green',
  Min: -85,
  Max: 45

} ).AddValueSliderField( {
  Name: 'Roll',
  Format: 'std',
  Digits: 3,
  Units: '&deg;',
  Color: 'green',
  Min: -45,
  Max: 45

} ).Render();


ControlPanels.NewPanel( {
  Name: 'Options',
  ModelRef: 'Model',
  NCols: 2,
  OnModelChange: UpdateAll

} ).AddRadiobuttonField( {
  Name: 'showModel',
  Label: 'Model',
  ValueType: 'int',
  Items: [
    {
      Name: 'Globe',
      Value: 1
    }, {
      Name: 'FlatEarth',
      Value: 2
    }, {
      Name: 'Globe+FE',
      Value: 3
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'viewcenterHorizon',
  Label: 'HorizView',
  ValueType: 'int',
  Items: [
    {
      Name: 'Globe',
      Value: 0
    }, {
      Name: 'Betwn',
      Value: 2
    }, {
      Name: 'FE-Eq',
      Value: 1
    }, {
      Name: 'Eye-Lvl',
      Value: 3
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'showGrid',
  Label: 'Grid',
  ValueType: 'int',
  Items: [
    {
      Name: 'Off',
      Value: 0
    }, {
      Name: 'On',
      Value: 1
    }, {
      Name: 'Projected',
      Value: 3
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'nLines',
  Label: 'Lines',
  ValueType: 'int',
  Items: [
    {
      Name: '15',
      Value: 15
    }, {
      Name: '30',
      Value: 30
    }, {
      Name: '45',
      Value: 45
    }, {
      Name: '60',
      Value: 60
    }, {
      Name: '90',
      Value: 90
    }
  ]

} ).AddCheckboxField( {
  Name: 'Show',
  Label: 'Show',
  Items: [
    {
      Name: 'showEyeLevel',
      Text: 'Eye-Level',
    }, {
      Name: 'showTangent',
      Text: 'Tangent',
    }, {
      Name: 'showFlatHorizon',
      Text: 'Prj-Horizon',
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'deviceRatio',
  Label: 'AspectRatio',
  ValueType: 'num',
  Items: [
    {
      Name: '3:2',
      Value: 3/2
    }, {
      Name: '2:3',
      Value: 2/3
    }, {
      Name: '16:9',
      Value: 16/9
    }, {
      Name: '9:16',
      Value: 9/16
    }
  ]

} ).Render();

</jscript>

{{NextTabBox}}

<jscript>

ControlPanels.NewSliderPanel( {
  Name: 'Object-Sliders',
  ModelRef: 'Model',
  OnModelChange: UpdateAll,
  Format: 'std',
  Digits: 3,
  ReadOnly: false,
  PanelFormat: 'InputMediumWidth'

} ).AddValueSliderField( {
  Name: 'NObjects',
  Color: 'black',
  Format: 'fix',
  Digits: 0,
  Min: 0,
  Max: 200,
  Steps: 200,

} ).AddValueSliderField( {
  Name: 'ObjDist',
  Label: 'Dist',
  ValueRef: 'ObjDist',
  SliderValueRef: 'Slider_ObjDist_Log',
  Units: 'm',
  Color: 'blue',
  Min: 0,
  Max: 5,

} ).AddValueSliderField( {
  Name: 'ObjDeltaDist',
  Label: 'DeltaDist',
  ValueRef: 'ObjDeltaDist',
  SliderValueRef: 'Slider_ObjDeltaDist_Log',
  Units: 'm',
  Color: 'blue',
  Min: 0,
  Max: 4,

} ).AddValueSliderField( {
  Name: 'ObjSidePos',
  Label: 'SidePos',
  ValueRef: 'ObjSidePos',
  SliderValueRef: 'Slider_ObjSidePos_Log',
  Units: 'm',
  Color: 'green',
  Min: -3,
  Max: 3,

} ).AddValueSliderField( {
  Name: 'ObjSideVar',
  Label: 'SideVar',
  ValueRef: 'ObjSideVar',
  SliderValueRef: 'Slider_ObjSideVar_Log',
  Units: 'm',
  Color: 'green',
  Min: -5,
  Max: 5,

} ).AddValueSliderField( {
  Name: 'ObjSize',
  ValueRef: 'ObjSize',
  SliderValueRef: 'Slider_ObjSize_Log',
  Units: 'm',
  Color: 'red',
  Min: 0,
  Max: 4,

} ).AddValueSliderField( {
  Name: 'ObjSizeVar',
  Label: 'SizeVar',
  Units: '%',
  Mult: 0.01,
  Color: 'red',
  Min: -1,
  Max: 1,

} ).Render();

</jscript>

<style>
#Object-Sliders .Row5 { background-color: #dfd; }
#Object-Sliders .Row7 { background-color: #fdd; }
#Object-Options .Row2 { background-color: #dfd; }
#Object-Options .Row2 .FieldGrid { background-color: #dfd; }
#Object-Options .Row2 .FieldCell { border-color: #dfd; }
#Object-Options .Row3 { background-color: #fdd; }
#Object-Options .Row3 .FieldGrid { background-color: #fdd; }
#Object-Options .Row3 .FieldCell { border-color: #fdd; }
</style>

<jscript>

ControlPanels.NewPanel( {
  Name: 'Object-Options',
  ModelRef: 'Model',
  NCols: 1,
  OnModelChange: UpdateAll

} ).AddRadiobuttonField( {
  Name: 'ObjType',
  Label: 'ObjType',
  ValueType: 'int',
  Items: [
    {
      Name: 'T-Tower',
      Value: 3
    }, {
      Name: 'City',
      Value: 4
    }, {
      Name: 'Mountain',
      Value: 5
    }, {
      Name: 'Bridge',
      Value: 6
    },{
      Name: 'Ship',
      Value: 2
    }, {
      Name: 'Bedford',
      Value: 0
    }, {
      Name: 'M-Rod',
      Value: 1
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'ObjSideType',
  Label: 'SideVar',
  ValueType: 'int',
  Items: [
    {
      Name: 'Lin',
      Value: 0
    }, {
      Name: '2-Col',
      Value: 1
    }, {
      Name: 'Rand',
      Value: 2
    }, {
      Name: 'Cos',
      Value: 3
    }, {
      Name: 'Sin',
      Value: 4
    }
  ]

} ).AddRadiobuttonField( {
  Name: 'ObjSizeType',
  Label: 'SizeVar',
  ValueType: 'int',
  Items: [
    {
      Name: 'Lin',
      Value: 0
    }, {
      Name: 'Alt',
      Value: 1
    }, {
      Name: 'Rand',
      Value: 2
    }, {
      Name: 'Cos',
      Value: 3
    }, {
      Name: 'Sin',
      Value: 4
    }
  ]

} ).Render();

</jscript>

#INCLUDE ModelAnimation.inc

<jscript>

/* Animations */

var CurrentAnimation = null;

function StartAnimation( animation ) {
  if (CurrentAnimation) StopAnimation();
  CurrentAnimation = animation;
  CurrentAnimation.Start();
}

function StopAnimation() {
  if (!CurrentAnimation) return;
  CurrentAnimation.Stop();
  CurrentAnimation = null;
}

function OnAnimationEnd() {
  Model.DemoText = '';
  Model.Description = '';
  Model.rEarth = 6371000;
  Model.Nick = 0;
  CurrentAnimation = null;
  UpdateAll( false );
}

// helper functions

function Ttxt( txt, delay ) {
  var obj1 = { ValueRef: 'Description', EndValue: '' };
  if (delay) obj1.Delay = delay;
  var obj2 = { Delay: 1000, ValueRef: 'Description', EndValue: txt };
  return {
    Mode: 'serial',
    TaskList: [ obj1, obj2 ],
  };        
}

function Tval( name, val, time, delay, sweep ) {
  var obj = { ValueRef: name, EndValue: val, Sweep: 'cosine' };
  if (time) obj.TimeSpan = time;
  if (delay) obj.Delay = delay;
  if (sweep) obj.Sweep = sweep;
  return obj;
}

function Tpan( delay, angle ) {
  angle = angle || 45;
  delay = delay || 0;
  return {
    Mode: 'serial',
    TaskList: [ 
      {
        Delay: delay,
        ValueRef: 'Pan',
        EndValue: -angle,
        TimeSpan: 2000,
        Sweep: 'cosine',
      },
      {
        ValueRef: 'Pan',
        EndValue: angle,
        TimeSpan: 4000,
        Sweep: 'cosine',
      },
      {
        ValueRef: 'Pan',
        EndValue: 0,
        TimeSpan: 2000,
        Sweep: 'cosine',
      },
    ],
  };        
}  

// Curve Animation

function InitCurveAnimation( anim ) {
  var m = Model;
  m.DemoText = 'Where is the Curve?';
  m.showModel = 1; // globe
  m.showEyeLevel = false;
  m.showGrid = 1; // on
  m.showFlatHorizon = false;
  m.showTangent = false;
  m.viewcenterHorizon = 0; // globe
  m.Height = 2;
  m.FocalLengthField = 33.9;
  m.Nick = 0;
  m.Roll = 0;
  m.Pan  = 0;
  m.rEarth = 6371000;
  
  m.ObjType = 0; // ???
  m.NObjects = 0;
  m.ObjDist = 0;
  m.ObjDeltaDist = 100;
  m.ObjSidePos = 0;
  m.ObjSideType = 0; // lin
  m.ObjSideVar = 0;
  m.ObjSize = 10;
  m.ObjSizeType = 0; // lin
  m.ObjSizeVar = 0;
  
  UpdateAll( false );
}

var CurveAnimation = NewAnimation( {
  ModelRef: Model,
  OnModelChange: function CB_OnModelChange_CurveAnimation(){ 
    UpdateAll(false); 
  },
  OnLoopInit: InitCurveAnimation,
  OnTaskEnd: OnAnimationEnd,
  RestartAction: 'continue',
  Mode: 'serial',
  TaskList: [
    Ttxt( 'This is the View of the Globe at Observer Height = 2 m, FoV = 65 deg' ),
    Tpan( 2000 ),
    Ttxt( 'Looks pretty flat, no Curvature visible. Lets go higher.' ),
    Tval( 'Height', 500, 3000, 3000 ),
    Ttxt( 'Height = 500 m. Still looking flat. Horizon at 80 km.', 1000 ),
    Tpan( 3000 ),
    Ttxt( 'Lets compaire with Flat Earth view.' ),
    Tval( 'showModel', 3, 0, 2000 ),
    Tval( 'showEyeLevel', true ),
    Ttxt( 'Looking flat too. But the Globe Horizon is NOT at Eye-Level!', 2000 ),
    Tpan( 3000 ),
    Ttxt( 'Lets climb to an Altitude of an Airplane at 12 km.', 1000 ),
    Tval( 'Height', 12000, 5000, 2000 ),
    Ttxt( 'The Globe Horizon is at 390 km now and has dropped to 3.5 degrees.', 1000 ),
    Tval( 'showModel', 1, 0, 5000 ),
    Ttxt( 'And we see a slightly bent Horizon!', 3000 ),
    Tval( 'showTangent', true, 0, 3000 ),
    Tpan( 2000 ),
    Ttxt( 'Lets Zoom in to f = 100 mm.', 1000 ),
    Tval( 'FocalLengthField', 100, 3000, 3000 ),
    Ttxt( 'Now the Horizon looks flat again. So the Field of View (Zooming) matters.', 1000 ),
    Tpan( 2000 ),
    Ttxt( 'Note that the Curvature is mainly down, not horizontally!', 2000 ),
    Ttxt( 'Lets climb to an Altitude of an amateur Rocket of 100 km.', 6000 ),
    Tval( 'Height', 100000, 5000, 3000 ),
    Ttxt( 'The Horizon has dropped to 10 degrees but still looks not much curved.', 1000 ),
    Tpan( 2000 ),
    Ttxt( 'Perhaps if we Zoom out again to 65 deg Field of View:', 1000 ),
    Tval( 'FocalLengthField', 33.9, 3000, 3000 ),
    Ttxt( 'Yes, clearly curved horizontally now.', 1000 ),
    Tpan( 2000 ),
    Ttxt( 'Lets compaire with Flat Earth again:', 1000 ),
    Tval( 'showModel', 3, 0, 3000 ),
    Ttxt( 'Even the Flat Earth Ice Wall Horizon dropped slightly', 3000 ),
    Ttxt( 'Lets climb to ISS Altitude of 400 km:', 5000 ),
    {
      Delay: 3000,
      Mode: 'parallel',
      TaskList: [
        Tval( 'Height', 400000, 5000 ),
        Tval( 'Nick', 20, 5000 ),
      ],
    },
    Ttxt( 'We should see the Ice Wall at 20015 km on Flat Earth! Globe Horizon at 2200 km.', 2000 ),
    Ttxt( 'Zooming (500 mm) makes the Horizon flat again, even at this Altitude!', 6000 ),
    Tval( 'showModel', 1, 0, 5000 ),
    Tval( 'Nick', 0, 3000 ),
    Tval( 'FocalLengthField', 500, 3000 ),
    Tval( 'showEyeLevel', false ),
    Tpan( 0, 15 ),
    Tval( 'FocalLengthField', 33.9, 3000, 1000 ),
    Ttxt( 'Going to Geostationary Orbit of 35786 km...', 1000 ),
    Tval( 'showTangent', false ),
    Tval( 'Height', 35786000, 10000, 2000 ),
    Ttxt( 'and back to 500 m', 3000 ),
    Tval( 'Height', 400000, 5000, 2000 ),
    Tval( 'Height', 12000, 5000 ),
    Tval( 'Height', 500, 5000 ),
  ],
} );

// Causeway

function InitCausewayAnimation( anim ) {
  var m = Model;
  m.DemoText = 'Lake Pontchartrain Causeway';
  m.showModel = 1; // globe
  m.showEyeLevel = false;
  m.showGrid = 0; // off
  m.showFlatHorizon = false;
  m.showTangent = false;
  m.viewcenterHorizon = 0; // globe
  m.Height = 100;
  m.FocalLengthField = 33.9;
  m.Nick = 0;
  m.Roll = 0;
  m.Pan  = 0;
  m.rEarth = 6371000;
  
  m.ObjType = 6; // bridge
  m.NObjects = 41;
  m.ObjDist = 0;
  m.ObjDeltaDist = 967.5;
  m.ObjSidePos = 45;
  m.ObjSideType = 1; // 2-col
  m.ObjSideVar = 30;
  m.ObjSize = 5;
  m.ObjSizeType = 0; // lin
  m.ObjSizeVar = 0;
  
  UpdateAll( false );
}

var CausewayAnimation = NewAnimation( {
  ModelRef: Model,
  OnModelChange: function CB_OnModelChange_CausewayAnimation(){ 
    UpdateAll(false); 
  },
  OnLoopInit: InitCausewayAnimation,
  OnTaskEnd: OnAnimationEnd,
  RestartAction: 'continue',
  Mode: 'serial',
  TaskList: [
    Ttxt( 'To prove that the Earth is flat...' ),
    Ttxt( 'they often use pictures like this of the 38.7 km long Causeway Bridge.', 3000 ),
    Ttxt( 'From this Perspective, not the slightest Curvature of the Earth is visible.', 5000 ),
    Tpan( 4000, 20 ),
    Ttxt( 'Lets compaire this Globe Simulation with the Flat Earth Simulation:', 1000 ),
    Tval( 'Description', '', 0, 5000 ),
    {
      Repeat: 2,
      TaskList: [
        Tval( 'Description', 'Flat Earth', 0, 1500 ),
        Tval( 'showModel', 2 ),
        Tval( 'viewcenterHorizon', 1 ),
        Tval( 'Description', 'Globe Earth', 0, 1500 ),
        Tval( 'showModel', 1 ),
        Tval( 'viewcenterHorizon', 0 ),
      ],
    },
    Tval( 'Description', 'Flat Earth', 0, 1500 ),
    Tval( 'showModel', 2 ),
    Tval( 'viewcenterHorizon', 1 ),
    Ttxt( 'Looks exactly the same! So where is the Curve hiding?', 1500 ),
    Ttxt( 'Lets compaire side by side and look for Differences:', 5000 ),
    Tval( 'showModel', 3, 0, 3000 ),
    Tval( 'showGrid', 1 ),
    Tval( 'viewcenterHorizon', 2 ),
    Tval( 'showEyeLevel', true ),
    Ttxt( 'Aha! The Globe Horizon is not at Eye-Level.', 4000 ),
    Ttxt( 'But can this be noticed with the naked Eye? Certainly not!', 4000 ),
    Ttxt( 'Lets zoom in to f = 300 mm and take a closer look:', 4000 ),
    Tval( 'FocalLengthField', 300, 3000, 4000 ),
    Ttxt( 'The Bridge Segments are all 967.5 m long, but look much shorter now.', 3000 ),
    Ttxt( 'This is a well known Effect of Perspective: Zoom compresses Distances.', 6000 ),
    Ttxt( 'And we see a slight Down-Bending at the end of the Bridge on the Globe.', 6000 ),
    Tval( 'FocalLengthField', 2000, 3000, 3000 ),
    Ttxt( 'The Bridge Segments go over the Horizon on the Globe!', 1000 ),
    Ttxt( 'We can enhance this Effect by lowering the Point of View to 2.5 m:', 4000 ),
    Tval( 'Height', 2.5, 5000, 4000 ),
    Tval( 'Description', '' ),
    Tval( 'ObjSidePos', 25, 5000 ),
    Ttxt( 'ZaVa (Zoom and View along) shows the Curvature of the Earth!' ),
    Tval( 'Description', '', 0, 8000 ),
  ],
} );

// TransLine Animation

function InitTransLineAnimation( anim ) {
  var m = Model;
  m.DemoText = 'Lake Pontchartrain Transmission Line';
  m.showModel = 1; // globe
  m.showEyeLevel = false;
  m.showGrid = 0; // off
  m.showFlatHorizon = false;
  m.showTangent = false;
  m.viewcenterHorizon = 0; // globe
  m.Height = 25;
  m.FocalLengthField = 50;
  m.Nick = 0;
  m.Roll = 0;
  m.Pan  = -20;
  m.rEarth = 6371000;
  
  m.ObjType = 3; // Transmission Tower
  m.NObjects = 85;
  m.ObjDist = 50;
  m.ObjDeltaDist = 288.3;
  m.ObjSidePos = -1000;
  m.ObjSideType = 0; // lin
  m.ObjSideVar = 0;
  m.ObjSize = 64;
  m.ObjSizeType = 0; // lin
  m.ObjSizeVar = 0;
  
  UpdateAll( false );
}

var TransLineAnimation = NewAnimation( {
  ModelRef: Model,
  OnModelChange: function CB_OnModelChange_TransLineAnimation(){ 
    UpdateAll(false); 
  },
  OnLoopInit: InitTransLineAnimation,
  OnTaskEnd: OnAnimationEnd,
  RestartAction: 'continue',
  Mode: 'serial',
  TaskList: [
    Ttxt( 'This Transmission Line crosses Lake Pontchartrain in 24.5 km.' ),
    {
      Mode: 'parallel',
      TaskList: [
        Tval( 'ObjSidePos', 1000, 20000, 0, 'linear' ),
        Tval( 'Pan', 20, 20000, 0, 'linear' ),
      ],
    },
    Ttxt( 'There are 85 identical Towers 288.3 m apart.' ),
    {
      Delay: 0,
      Mode: 'parallel',
      TaskList: [
        Tval( 'FocalLengthField', 300, 5000, 0 ),
        Tval( 'Pan', 5, 5000, 0, 'linear' ),
      ],
    },
    Ttxt( 'The last few Towers curve down behind the Horizon.' ),
    {
      Delay: 0,
      Mode: 'parallel',
      TaskList: [
        Tval( 'ObjSidePos', 50, 10000, 0 ),
        Tval( 'Pan', 0, 10000, 0 ),
      ],
    },
    Ttxt( 'Zooming shows the Curvature clearly.' ),
    Tval( 'FocalLengthField', 1000, 5000, 3000 ),
    Tval( 'showGrid', 1, 0, 3000 ),
    Ttxt( 'Lets compaire with Flat Earth.', 3000 ),
    Tval( 'showModel', 3, 0, 3000 ),
  ],
} );

// Bedford Animation

function InitBedfordAnimation( anim ) {
  var m = Model;
  m.DemoText = 'Bedford Level Experiment';
  m.showModel = 3; // globe + FE
  m.showEyeLevel = false;
  m.showGrid = 0; // none
  m.showFlatHorizon = false;
  m.showTangent = false;
  m.viewcenterHorizon = 3; // eye level
  m.Height = 7;
  m.FocalLengthField = 800;
  m.Nick = 0;
  m.Roll = 0;
  m.Pan  = 0;
  m.rEarth = 6371000;
  
  m.ObjType = 0; // Bedford
  m.NObjects = 3;
  m.ObjDist = -8600;
  m.ObjDeltaDist = 4830;
  m.ObjSidePos = 0;
  m.ObjSideType = 0; // lin
  m.ObjSideVar = 18;
  m.ObjSize = 4.04;
  m.ObjSizeType = 0; // lin
  m.ObjSizeVar = 0;
  
  UpdateAll( false );
}

var BedfordAnimation = NewAnimation( {
  ModelRef: Model,
  OnModelChange: function CB_OnModelChange_BedfordAnimation(){ 
    UpdateAll(false); 
  },
  OnLoopInit: InitBedfordAnimation,
  OnTaskEnd: OnAnimationEnd,
  RestartAction: 'continue',
  Mode: 'serial',
  TaskList: [
    {
      Delay: 2000,
      Mode: 'parallel',
      TaskList: [
        Tval( 'Description', 'Bridge with Marker' ),
        Tval( 'Description', 'Middle Marker', 0, 2000 ),
        Tval( 'Description', 'and Telescope', 0, 4000 ),
        Tval( 'Description', 'are at the same height (4 m) and 4830 m apart', 0, 6000 ),
        Tval( 'Height', 5, 6000 ),
        Tval( 'ObjDist', 370, 6000 ),
        Tval( 'ObjSidePos', 11, 6000 ),
        Tval( 'Height', 4.04, 2000, 9000 ),
        Tval( 'ObjSidePos', 9.23, 2000, 9000 ),
        Tval( 'ObjDist', 124, 2000, 9000 ),
      ],
    },
    Tval( 'FocalLengthField', 5000, 2000, 2000 ),
    Ttxt( 'On the Globe, the middle Marker appears ABOVE the Bridge Marker', 1000 ),
    Tval( 'Height', 4.135, 1000, 3000 ),
    Ttxt( 'but both Markers appear below Eye-Level', 5000 ),
    Tval( 'showEyeLevel', true, 0, 3000 ),
    Tval( 'Height', 4.04, 1000, 1000 ),
    Ttxt( 'Refraction makes the scene appear more flat', 5000 ),
    Tval( 'rEarth', 6371000 * 7 / 2, 1000, 3000 ),
    Ttxt( 'Note: the bridge appears no longer behind the horizon', 3000 ),
    Ttxt( 'Whithout Refraction the bridge is partially hidden behind the horizon', 5000 ),
    Tval( 'showGrid', 1, 0, 3000 ),
    Tval( 'rEarth', 6371000, 1000, 1000 ),
    Ttxt( 'With more Markers (every 200 m)...', 5000 ),
    { 
      Delay: 3000,
      Mode: 'parallel',
      TaskList: [
        Tval( 'ObjSidePos', 5.63, 1000 ),
        Tval( 'ObjSideVar', 1.25, 1000 ),
      ],
    },
    Tval( 'NObjects', 49, 0, 1000 ),
    Tval( 'ObjDeltaDist', 201.25 ),
    Ttxt( 'The curvature of the Globe Earth is clearly visible', 3000 ),
    Tval( 'ObjSideVar', 7.66, 4000, 3000 ),
    Tval( 'ObjSideVar', 1.25, 4000, 2000 ),
  ],
} );

// City Animation

function InitCityAnimation( anim ) {
  var m = Model;
  m.DemoText = 'Chicago Skyline';
  m.showModel = 3; // globe + FE
  m.showEyeLevel = true;
  m.showGrid = 1; // on
  m.viewcenterHorizon = 2; // between
  m.Height = 2;
  m.FocalLengthField = 34;
  m.Nick = 0;
  m.Roll = 0;
  m.Pan  = 0;
  m.rEarth = 6371000;
  
  m.ObjType = 4; // city
  m.NObjects = 6;
  m.ObjDist = 3400;
  m.ObjDeltaDist = 200;
  m.ObjSidePos = 1040;
  m.ObjSideType = 2; // rand
  m.ObjSideVar = -30.5;
  m.ObjSize = 442;
  m.ObjSizeType = 3; // cos
  m.ObjSizeVar = 0.381;
  
  UpdateAll( false );
}

var CityAnimation = NewAnimation( {
  ModelRef: Model,
  OnModelChange: function CB_OnModelChange_CityAnimation(){ 
    UpdateAll(false); 
  },
  OnLoopInit: InitCityAnimation,
  OnTaskEnd: OnAnimationEnd,
  RestartAction: 'continue',
  Mode: 'serial',
  TaskList: [
    Ttxt( 'Observer Altitude = 2 m, City Distance = 3.4 km, Tallest Building = 442 m' ),
    Ttxt( 'Lets cross Lake Michigan to Grand Mere State Park', 5000 ),
    Tval( 'ObjDist', 91100, 5000, 3000 ),
    Ttxt( 'Distance = 91.1 km', 1000 ),
    Ttxt( 'On the Globe, Chicago disappeared behind the Curvature of the Earth', 3000 ),
    Ttxt( 'Zooming in (f = 800 mm) does not bring the Skyline back into view', 4000 ),
    Tval( 'FocalLengthField', 800, 5000, 3000 ),
    Ttxt( 'Lets climb a nearby Hill of Height = 200 m', 1000 ),
    Tval( 'Height', 200, 5000, 3000 ),
    Ttxt( 'Now 313 m of the Skyline comes into view, 129 m are still hidden', 1000 ),
    Ttxt( 'Note that the whole Skyline is BELOW Eye-Level', 5000 ),
    Ttxt( 'Lets go down to the Beach (2 m) again', 5000 ),
    Tval( 'Height', 2, 5000, 3000 ),
    Ttxt( 'Now lets apply severe Refraction of 3.5 R (Looming)', 1000 ),
    Tval( 'rEarth', 6371000 * 3.5, 3000, 3000 ),
    Ttxt( 'Now 293 m of the Skyline comes into view again, 149 m are hidden', 1000 ),
    Ttxt( 'Lets remove Refraction and climb to 660 m', 5000 ),
    Tval( 'rEarth', 6371000, 3000, 3000 ),
    Tval( 'Height', 660, 5000, 2000 ),
    Ttxt( 'At an Altitude of 660 m the whole City is now visible.', 1000 ),
    Ttxt( 'On the Flat Earth the Skyline never disapeared!', 5000 ),
    Tval( 'Description', '', 0, 5000 ),
  ],
} );

</jscript>

{{NextTabBox}}

<jscript>

function CLengthModel() {
  this.m = 0;
  this.km = 0;
  this.sm = 0;
  this.mile = 0;
  this.in = 0;
  this.cm = 0;
  this.ft = 0;
  this.fl = 0;
  this.mLast = 0;
  this.kmLast = 0;
  this.smLast = 0;
  this.mileLast = 0;
  this.inLast = 0;
  this.cmLast = 0;
  this.ftLast = 0;
  this.flLast = 0;
}

CLengthModel.prototype.calcOthersFromMeter = function() {
  this.km = this.m / 1000;
  this.sm = this.m / 1852;
  this.mile = this.m / 1609.344;
  this.in = this.m / 0.0254;
  this.cm = this.m * 100;
  this.ft = this.m / 0.3048;
  this.fl = this.ft / 100;
}

CLengthModel.prototype.Update = function() {
  if (this.m != this.mLast) {
    this.calcOthersFromMeter();
  } 
  else if (this.km != this.kmLast) {
    this.m = this.km * 1000;
    this.calcOthersFromMeter();
  }
  else if (this.sm != this.smLast) {
    this.m = this.sm * 1852;
    this.calcOthersFromMeter();
  }
  else if (this.mile != this.mileLast) {
    this.m = this.mile * 1609.344;
    this.calcOthersFromMeter();
  }
  else if (this.in != this.inLast) {
    this.m = this.in * 0.0254;
    this.calcOthersFromMeter();
  } 
  else if (this.cm != this.cmLast) {
    this.m  = this.cm / 100;
    this.calcOthersFromMeter();
  } 
  else if (this.ft != this.ftLast) {
    this.m = this.ft * 0.3048;
    this.calcOthersFromMeter();
  } 
  else if (this.fl != this.flLast) {
    this.m = this.fl * 100 * 0.3048;
    this.calcOthersFromMeter();
  }
  this.mLast = this.m;
  this.kmLast = this.km;
  this.smLast = this.sm;
  this.mileLast = this.mile;
  this.inLast = this.in;
  this.cmLast = this.cm;
  this.ftLast = this.ft;
  this.flLast = this.fl;
}

var LengthModel = new CLengthModel();

function UpdateLengthModel() {
  LengthModel.Update();
  ControlPanels.Update( 'Units-Calculator' );
}

xOnLoad( UpdateLengthModel );


ControlPanels.NewPanel( {
    Name: 'Units-Calculator',
    ModelRef: 'LengthModel',
    OnModelChange: UpdateLengthModel,
    NCols: 2,
    Format: 'std',
    Digits: 10
  }

).AddTextField( {
    Name: 'm',
    Label: 'Length',
    Units: 'm'
  }

).AddTextField( {
    Name: 'km',
    Label: '-',
    Units: 'km'
  }

).AddTextField( {
    Name: 'sm',
    Label: '(sea mile)',
    Units: 'sm'
  }

).AddTextField( {
    Name: 'mile',
    Label: '(statute)',
    Units: 'mile'
  }

).AddTextField( {
    Name: 'in',
    Label: '-',
    Units: 'in (Zoll)'
  }

).AddTextField( {
    Name: 'cm',
    Label: '',
    Units: 'cm'
  }

).AddTextField( {
    Name: 'ft',
    Label: '-',
    Units: 'ft'
  }

).AddTextField( {
    Name: 'fl',
    Label: 'FL',
    Units: ''
  }

).Render();

</jscript>

{{EndTabBoxes}}

== Computed Values ==

<jscript>

ControlPanels.NewPanel( {
  Name: 'Output',
  ModelRef: 'Model',
  OnModelChange: UpdateAll,
  NCols: 2,
  ReadOnly: true,
  Format: 'std',
  Digits: 4

} ).AddTextField( {
  Name: 'hDip',
  Label: 'DipHeight&nbsp;b',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'aDip',
  Label: 'DipAngle&nbsp;&alpha;',
  Mult: Math.PI / 180,
  Units: '&deg;'

} ).AddTextField( {
  Name: 'dHorizon',
  Label: 'HorDist&nbsp;s',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'aEarth',
  Label: 'AngDiameter',
  Mult: Math.PI / 180,
  Units: '&deg;'

} ).AddTextField( {
  Name: 'rDisk',
  Label: 'HorDistX&nbsp;d',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'dDelta',
  Label: 'LineSpacing',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'zDisk',
  Label: 'HorDistZ&nbsp;p',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'dView',
  Label: 'HorDistView&nbsp;v',
  Mult: 1000,
  Units: 'km'

} ).AddTextField( {
  Name: 'rEarth',
  Label: 'RadiusPlanet',
  Mult: 1000,
  Units: 'km',
  ReadOnly: false

} ).Render();

</jscript>

Weitere Infos zur Seite
Erzeugt Dienstag, 31. Januar 2017
von wabis
Zum Seitenanfang
Geändert Dienstag, 8. August 2017
von wabis