WaBis

walter.bislins.ch

Source Code: Equinox Sundial

This is the source code for the sundial animation of the page Creating an Equinox Sundial made of Paper.

The description of the graphic module JsGraphX3D, the ControlPanels module and the EarthMap module can be found here:

#INCLUDE JsGraphX3D.inc
#INCLUDE EarthMap.inc
#INCLUDE ControlPanel.inc

<jscript>

function toRad( a ) { return a * Math.PI / 180; }

var Sundial = {

  Latitude: 15,
  Longitude: 8,
  Azimuth: 0,
  DeltaAngle: 0,
  Time: 12,
  EarthTilt: 0, // tilt of earth axis wrt sun: -23.44° .. 23.44°

  EarthRadius: 1000,
  ShadowAlpha: 0.8,
  ShadowColor: 'gray',

  // private

  EarthViewTilt: 0,
  AngleToNoon: 0, // sun angle relative to 12:00
  MaxEarthTilt: 23.44,
  GlobalSunDir: [ 0, 0, 0 ],

  graph: null,

  Create: function() {
    var me = this;
    this.graph = NewGraphX3D( {
      Width: '100%',
      Height: '66%',
      DrawFunc: function(){ me.Draw(); },
      AutoReset: false,
      AutoClear: false,
      AutoScalePix: true
    } );
  },

  Update: function() {
    if (this.Latitude > 90) this.Latitude = 90;
    if (this.Latitude < -90) this.Latitude = -90;
    if (this.Longitude > 180) this.Longitude = 180;
    if (this.Longitude < -180) this.Longitude = -180;
    if (this.Azimuth > 10) this.Azimuth = 10;
    if (this.Azimuth < -10) this.Azimuth = -10;
    if (this.DeltaAngle > this.MaxEarthTilt) this.DeltaAngle = this.MaxEarthTilt;
    if (this.DeltaAngle < -this.MaxEarthTilt) this.DeltaAngle = -this.MaxEarthTilt;
    if (this.Latitude+this.DeltaAngle > 90) this.DeltaAngle = 90-this.Latitude;
    if (this.Latitude+this.DeltaAngle < -90) this.DeltaAngle = -90-this.Latitude;
    if (this.Time > 17.9999) this.Time = 17.9999;
    if (this.Time < 6.0001) this.Time = 6.0001;
    if (this.EarthTilt > this.MaxEarthTilt) this.EarthTilt = this.MaxEarthTilt;
    if (this.EarthTilt < -this.MaxEarthTilt) this.EarthTilt = -this.MaxEarthTilt;
    if (Math.abs(this.Azimuth) < 0.4) this.Azimuth = 0;
    if (Math.abs(this.DeltaAngle) < 0.5) this.DeltaAngle = 0;
    if (Math.abs(this.EarthTilt) < 0.6) this.EarthTilt = 0;
    this.EarthViewTilt = this.Latitude / 2 - 45;
    this.AngleToNoon = (this.Time - 12) * 180 / 12;
  },

  Draw: function() {
    var g = this.graph;
    g.Reset3D();

    g.SetAngleMeasure( 'deg' );
    g.SetGraphClipping( true, '', 0 );
    g.SetWindowToCameraScreen();

    g.SetCamera( {
      SceneSize: 2000,
      CamPos: [ 200000, 100000, 20000 ],
      CamUp: [ 0, 0, 1 ],
      CamViewCenter: [ 0, -150, this.EarthRadius/2 ],
    } );
    g.SetCameraZoom( 1.3 );

    this.DrawEarth( g );
    this.DrawSundial( g );
  },

  DrawEarth: function( g ) {

    // set earth transformation: earth rotation and tilt
    g.TransRotateZ3D( -this.Longitude );
    g.TransRotateY3D( this.EarthViewTilt );
    EarthMap.Trans = g.Trans3D;
    g.ResetTrans3D();

    // draw earth
    EarthMap.Radius = this.EarthRadius;
    EarthMap.SetWaterColor( '#d3e2f5' );
    EarthMap.SetLakeColor( '#d3e2f5', '#8cbe5d' );
    EarthMap.SetContinentColor( null, '#c6dfaf', '#8cbe5d' );
    EarthMap.SetLandColor( 'Antarctica', '#eee', '#ccc' );

    EarthMap.DrawGlobe( g );

    // draw earth shadow
    var sunDir = EarthMap.PointOnGlobeEF( this.EarthTilt, this.Longitude - this.AngleToNoon );
    g.SetAreaAttr( 'black', 'black' );
    g.SetAlpha( 0.3 );
    EarthMap.DrawGlobeShadow( g, sunDir, 0 );

    // draw earth grids
    g.SetAlpha( 0.3 );
    g.SetLineAttr( 'gray', 1 );
    EarthMap.DrawGlobeGrid( g, 15, 15 );
    g.SetLineAttr( 'black', 1 );
    EarthMap.DrawGlobeEquator( g );
    EarthMap.DrawGlobeMeridian( g );
    g.SetAlpha( 1 );

    // draw north arrow of celestial sphere
    var celestSpherePos = [ 0, -1.1*this.EarthRadius, 0.85*this.EarthRadius ];
    var celestSphereRadius = 0.25 * this.EarthRadius;
    g.TransRotateY3D( this.EarthViewTilt );
    g.TransMove3D( celestSpherePos );
    g.SetAreaAttr( 'red', 'red', 2 );
    g.SetMarkerSymbol( 'Arrow2' );
    g.Arrow3D( [ 0, 0, -celestSphereRadius*1.3 ], [ 0, 0, celestSphereRadius*1.3 ] );
    g.SetTextAttr( 'Arial', 16, 'black', 'normal', 'bold', 'center', 'middle' );
    g.Text3D( 'N', [ 0, 0, 1.45*celestSphereRadius ] );

    // draw earth symbol on celestial sphere
    g.SetMarkerAttr( 'Circle', 16, 'blue', 'lightblue', 1 );
    g.Marker3D( [ 0, 0, 0 ], 3 );
    g.ResetTrans3D();

    // draw celestial sphere in two halfs, back side transparent, front opaque
    var toCam = JsgVect3.Norm( JsgVect3.Sub( g.Camera.CamPos, celestSpherePos ) );
    var clipX = JsgVect3.Norm( JsgVect3.Mult( [ 0, 0, 1 ], toCam ) );
    var clipY = JsgVect3.Norm( JsgVect3.Mult( toCam, clipX ) );

      // draw back side
      g.AddClipPlane( celestSpherePos, clipX, JsgVect3.Scale( clipY, -1 ) );
      this.DrawCelestialSphere( g, celestSphereRadius, celestSpherePos, 0.2 );
      g.DeleteClipPlanes();

      // draw front side
      g.AddClipPlane( celestSpherePos, clipX, clipY );
      this.DrawCelestialSphere( g, celestSphereRadius, celestSpherePos, 0.6 );
      g.DeleteClipPlanes();

  },

  DrawCelestialSphere: function( g, celestialSphereRadius, celestialSpherePos, alpha ) {

    var gridAngleInc = 10;

    // set transformation for celectial grid
    var dateRotation = 90 * this.EarthTilt / this.MaxEarthTilt;
    var timeDateRotation = dateRotation + this.AngleToNoon;
    g.TransRotateZ3D( -timeDateRotation );
    g.TransRotateY3D( this.EarthViewTilt );
    g.TransMove3D( celestialSpherePos );

    // draw longitude lines
    g.SetAreaAttr( 'gray', 'gray', 1 );
    g.SetAlpha( alpha );
    var maxGridAngle = 180 - gridAngleInc / 2;
    for (var gridAngle = 0; gridAngle < maxGridAngle; gridAngle += gridAngleInc) {
      var gridAngleRad = toRad( gridAngle );
      var planeXAxes = [ Math.cos( gridAngleRad ), Math.sin( gridAngleRad ), 0 ];
      g.SetPlane( [ 0, 0, 0 ], planeXAxes, [ 0, 0, 1 ] );
      g.CircleOnPlane( 0, 0, celestialSphereRadius, 1 );
    }

    // draw latitude lines
    maxGridAngle = 90 - 1.5 * gridAngleInc;
    for (var gridAngle = -90 + gridAngleInc; gridAngle < maxGridAngle; gridAngle += gridAngleInc) {
      var gridAngleRad = toRad( gridAngle );
      var planeYPos = [ 0, 0, celestialSphereRadius * Math.sin( gridAngleRad ) ];
      var gridLatitudeRadius = celestialSphereRadius * Math.cos( gridAngleRad );
      g.SetPlane( planeYPos, [ 1, 0, 0 ], [ 0, 1, 0 ] );
      g.CircleOnPlane( 0, 0, gridLatitudeRadius, 1 );
    }

    // equator
    if (alpha > 0.5) g.SetAlpha( 0.8 );
    g.SetLineAttr( 'black', 1.5 );
    g.SetPlane( [ 0, 0, 0 ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
    g.CircleOnPlane( 0, 0, celestialSphereRadius, 1 );

    // june solstice
    var solsticeAngle = toRad( this.MaxEarthTilt );
    var planeYPos = [ 0, 0, celestialSphereRadius * Math.sin( solsticeAngle ) ];
    var solsticeLatitudeRadius = celestialSphereRadius * Math.cos( solsticeAngle );
    g.SetPlane( planeYPos, [ 1, 0, 0 ], [ 0, 1, 0 ] );
    g.SetLineAttr( 'black', 1 );
    g.CircleOnPlane( 0, 0, solsticeLatitudeRadius, 1 );

    // december solstice
    var solsticeAngle = toRad( -this.MaxEarthTilt );
    var planeYPos = [ 0, 0, celestialSphereRadius * Math.sin( solsticeAngle ) ];
    var solsticeLatitudeRadius = celestialSphereRadius * Math.cos( solsticeAngle );
    g.SetPlane( planeYPos, [ 1, 0, 0 ], [ 0, 1, 0 ] );
    g.CircleOnPlane( 0, 0, solsticeLatitudeRadius, 1 );
    g.ResetTrans3D();
    g.SetAlpha( alpha );

    // set transformation for ecliptic and sun position
    g.TransRotateX3D( this.MaxEarthTilt );
    g.TransRotateZ3D( -timeDateRotation );
    g.TransRotateY3D( this.EarthViewTilt );
    g.TransMove3D( celestialSpherePos );

    // draw ecliptic and sun direction
    g.SetAlpha( alpha > 0.5 ? 1 : 0.5 );
    g.SetPlane( [ 0, 0, 0 ], [ 1, 0, 0 ], [ 0, 1, 0 ] ); // ecliptic plane
    g.SetAreaAttr( 'orange', 'orange', 2 );
    g.CircleOnPlane( 0, 0, celestialSphereRadius, 1 );
    var dateRotationAngle = toRad( dateRotation );
    var sunPosX = celestialSphereRadius * Math.cos( dateRotationAngle );
    var sunPosY = celestialSphereRadius * Math.sin( dateRotationAngle );
    g.SetAlpha( 1 );
    g.SetMarkerAttr( 'Arrow1', 8, 'orange', 'orange', 2 );
    g.ArrowOnPlane( [ sunPosX, sunPosY, 0 ], [ 0.2*sunPosX, 0.2*sunPosY, 0 ] );
    g.SetMarkerAttr( 'Circle', 12, 'orange', 'white', 2 );
    g.Marker3D( [ sunPosX, sunPosY, 0 ], 3 );

    this.GlobalSunDir = JsgVect3.Sub( JsgMat3.Trans( g.Trans3D, [ sunPosX, sunPosY, 0 ] ), JsgMat3.Trans( g.Trans3D, [ 0, 0, 0 ] ) );

    g.ResetTrans3D();
  },

  DrawSundial: function( g ) {

    var sqrt2 = Math.sqrt(2);

    // get earth surface trans
    g.TransMove3D( [ 0, 0, this.EarthRadius ] );
    g.TransRotateY3D( -this.EarthViewTilt );
    var earthSufaceTrans = g.Trans3D;
    g.ResetTrans3D();

    // get sundial base trans
    g.TransRotateZ3D( -this.Azimuth );
    g.AddTrans3D( earthSufaceTrans );
    var sundialBaseTrans = g.Trans3D;
    g.ResetTrans3D();

    // get sundial top trans
    g.TransRotateY3D( this.Latitude + this.DeltaAngle );
    g.TransMove3D( [ 0, 0, 100 ] );
    g.AddTrans3D( sundialBaseTrans );
    var sundialTopTrans = g.Trans3D;
    g.ResetTrans3D();

    // draw earth surface plane
    g.SetAreaAttr( '#8cbe5d', 'black', 1 );
    g.SetTrans3D( earthSufaceTrans );
    g.SetPlane( [ 0, 0, 0 ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
    g.RectOnPlane( -150, -150, 150, 150, 3 );

    // draw sundial base
    g.SetTrans3D( sundialBaseTrans );
    g.SetAreaAttr( 'white', 'black', 1 );

      // bottom
      g.SetPlane( [ 0, 0, 0 ], [ 1, 0, 0 ], [ 0, 1, 0 ] );
      g.RectOnPlane( -100, -100, 100, 100, 3 );

      // back
      var d = 80 / sqrt2;
      g.SetPlane( [ 0, -100, 0 ], [ 1, 0, 0 ], [ 0, 0, 1 ] );
      g.PolygonOnPlane(
        [ -100, 100, 100,     d,  d, -d,    -d, -100, -100 ],
        [    0,   0,  20, 100-d, 20, 20, 100-d,   20,    0 ], 3 );
      g.SetPlane( [ -100, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] );
      g.RectOnPlane( -100, 0, 100, 20, 3 );
      g.SetPlane( [ -100, 0, 20 ], [ 0, 1, 0 ], [ -(d-100), 0, 80-d ], true );
      g.RectOnPlane( -100, 0, 100, 49.3, 3 );

      // front
      g.SetPlane( [ 100, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] );
      g.RectOnPlane( -100, 0, 100, 20, 3 );
      g.SetPlane( [ 100, 0, 20 ], [ 0, 1, 0 ], [ d-100, 0, 80-d ], true );
      g.RectOnPlane( -100, 0, 100, 49.3, 3 );
      g.SetPlane( [ 0, 100, 0 ], [ 1, 0, 0 ], [ 0, 0, 1 ] );
      g.PolygonOnPlane(
        [ -100, 100, 100,     d,  d, -d,    -d, -100, -100 ],
        [    0,   0,  20, 100-d, 20, 20, 100-d,   20,    0 ], 3 );


    // draw top part
    g.SetTrans3D( sundialTopTrans );

      // compute sun direction vector in the to top part coordinate system
      var vo = JsgMat3.Trans( g.Trans3D, [ 0, 0, 0 ] );
      var vx = JsgVect3.Sub( JsgMat3.Trans( g.Trans3D, [ 1, 0, 0 ] ), vo );
      var vy = JsgVect3.Sub( JsgMat3.Trans( g.Trans3D, [ 0, 1, 0 ] ), vo );
      var vz = JsgVect3.Sub( JsgMat3.Trans( g.Trans3D, [ 0, 0, 1 ] ), vo );
      var transToLocal = JsgMat3.FromVect( vx, vy, vz );
      var localSunDir = JsgMat3.Trans( transToLocal, this.GlobalSunDir );

      // back protractor
      g.SetPlane( [ 0, -80, 0 ], [ -1, 0, 0 ], [ 0, 0, 1 ] );
      g.OpenPath3D();
      g.PolygonOnPlane( [ 80, 80, -80, -80 ], [ 0, 20, 20, 0 ], 3 );
      g.ArcOnPlane( 0, 0, 80, 180, 360 );
      g.Path3D( 3 );

      // front protractor
      g.SetPlane( [ 0, 80, 0 ], [ -1, 0, 0 ], [ 0, 0, 1 ] );
      g.OpenPath3D();
      g.PolygonOnPlane( [ 80, 80, -80, -80 ], [ 0, 20, 20, 0 ], 3 );
      g.ArcOnPlane( 0, 0, 80, 180, 360 );
      g.Path3D( 3 );

      // top plates

        function addLocalClipPlane( o, x, y ) {
          // o, x, y are local plane coordinates. o = origin, x = x-axis, y = y-axis of clipping plane
          // Local coordinates are transformed to global coordinates via the current Trans3D for the clipping planes
          var px = JsgVect3.Add( o, x );
          var py = JsgVect3.Add( o, y );
          var planeO = JsgVect3.Copy( g.TransPoint3D( o ) );
          var planeX = JsgVect3.Sub( g.TransPoint3D( px ), planeO );
          var planeY = JsgVect3.Sub( g.TransPoint3D( py ), planeO );
          g.AddClipPlane( planeO, planeX, planeY );
        }

        function toPlaneCoord( p ) {
          // projects p to g.Plane and returns [x,y] of p on the plane
          var v = JsgVect3.Sub( p, g.Plane.Pos );
          var x = JsgVect3.ScalarProd( v, g.Plane.XDir );
          var y = JsgVect3.ScalarProd( v, g.Plane.YDir );
          return [ x, y ];
        }

        function drawShadow( p1, p2, p3, p4 ) {
          // Draws the shadow of the rectangle given by p1..p4 on the current g.Plane
          // The shadow is the projection of the rectangle to the g.Plane in the direction of localSunDir
          var q1 = JsgVect3.Copy( g.Plane.IntersectLine( p1, JsgVect3.Add( p1, localSunDir ) ) );
          var q2 = JsgVect3.Copy( g.Plane.IntersectLine( p2, JsgVect3.Add( p2, localSunDir ) ) );
          var q3 = JsgVect3.Copy( g.Plane.IntersectLine( p3, JsgVect3.Add( p3, localSunDir ) ) );
          var q4 = JsgVect3.Copy( g.Plane.IntersectLine( p4, JsgVect3.Add( p4, localSunDir ) ) );
          if (q1 == null || q2 == null || q3 == null || q4 == null) return; // sun lies in the g.Plane
          var q1plane = toPlaneCoord( q1 );
          var q2plane = toPlaneCoord( q2 );
          var q3plane = toPlaneCoord( q3 );
          var q4plane = toPlaneCoord( q4 );
          g.PolygonOnPlane(
            [ q1plane[0], q2plane[0], q3plane[0], q4plane[0] ],
            [ q1plane[1], q2plane[1], q3plane[1], q4plane[1] ], 7 );
        }

        function sunIsOnTopOfPlane() {
          // returns true if sun is on top of g.Plane, not behind
          return JsgVect3.ScalarProd( localSunDir, g.Plane.Normal ) > 0;
        }

        // left plate
        g.SetPlane( [ 0, -100, 20 ], [ -1, 0, 0 ], [ 0, -1, 1 ], true );

          // plate background
          g.SetAreaAttr( 'white', 'black', 1 );
          g.RectOnPlane( -100, 0, 100, 100, 3 );

          // plate shadows
          var d = 100 / sqrt2;
          addLocalClipPlane( [ -100, -100, 20 ], [ 0, 1, 0 ], [ 0, 0, 1 ] );
          addLocalClipPlane( [ 100, -100, 20 ], [ 0, 1, 0 ], [ 0, 0, -1 ] );
          addLocalClipPlane( [ 0, -100-d, 20+d ], [ 1, 0, 0 ], [ 0, -1, -1 ] );
          addLocalClipPlane( [ 0, -100, 20 ], [ 1, 0, 0 ], [ 0, 1, 1 ] );
          g.SetAreaAttr( this.ShadowColor, this.ShadowColor, 1 );
          g.SetAlpha( this.ShadowAlpha );
          if (sunIsOnTopOfPlane()) {
            // gonomon
            drawShadow( [ -30, -100, 20 ], [ -30, 0, 120 ], [ -10, 0, 120 ], [ -10, -100, 20 ] );
            drawShadow( [ -30, 0, 120 ], [ -30, 100, 20 ], [ -10, 100, 20 ], [ -10, 0, 120 ] );
            drawShadow( [ 10, -100, 20 ], [ 10, 0, 120 ], [ 30, 0, 120 ], [ 30, -100, 20 ] );
            drawShadow( [ 10, 0, 120 ], [ 10, 100, 20 ], [ 30, 100, 20 ], [ 30, 0, 120 ] );
            // gonomon bar
            drawShadow( [ -10, -14, 106 ], [ -10, 0, 120 ], [ 10, 0, 120 ], [ 10, -14, 106 ] );
            drawShadow( [ -10, 0, 120 ], [ -10, 14, 106 ], [ 10, 14, 106 ], [ 10, 0, 120 ] );
            // other plates
            drawShadow( [ -100, -100, 20 ], [ -100, 100, 20 ], [ 100, 100, 20 ], [ 100, -100, 20 ] );
            drawShadow( [ -100, 100, 20 ], [ -100, 100+d, 20+d ], [ 100, 100+d, 20+d ], [ 100, 100, 20 ] );
          } else {
            // plate is in full shadow
            g.RectOnPlane( -100, 0, 100, 100, 3 );
          }
          g.SetAlpha( 1 );
          g.DeleteClipPlanes();

        // center plate
        g.SetPlane( [ 0, 0, 20 ], [ 1, 0, 0 ], [ 0, 1, 0 ] );

          // plate background
          g.SetAreaAttr( 'white', 'black', 1 );
          g.RectOnPlane( -100, -100, 100, 100, 3 );

          // plate shadows
          var d = 100 / sqrt2;
          addLocalClipPlane( [ -100, 0, 20 ], [ 0, 1, 0 ], [ 0, 0, 1 ] );
          addLocalClipPlane( [ 100, 0, 20 ], [ 0, 1, 0 ], [ 0, 0, -1 ] );
          addLocalClipPlane( [ 0, -100, 20 ], [ 1, 0, 0 ], [ 0, 0, -1 ] );
          addLocalClipPlane( [ 0, 100, 20 ], [ 1, 0, 0 ], [ 0, 0, 1 ] );
          g.SetAreaAttr( this.ShadowColor, this.ShadowColor, 1 );
          g.SetAlpha( this.ShadowAlpha );
          if (sunIsOnTopOfPlane()) {
            // gonomon
            drawShadow( [ -30, -100, 20 ], [ -30, 0, 120 ], [ -10, 0, 120 ], [ -10, -100, 20 ] );
            drawShadow( [ -30, 0, 120 ], [ -30, 100, 20 ], [ -10, 100, 20 ], [ -10, 0, 120 ] );
            drawShadow( [ 10, -100, 20 ], [ 10, 0, 120 ], [ 30, 0, 120 ], [ 30, -100, 20 ] );
            drawShadow( [ 10, 0, 120 ], [ 10, 100, 20 ], [ 30, 100, 20 ], [ 30, 0, 120 ] );
            // gonomon bar
            drawShadow( [ -10, -14, 106 ], [ -10, 0, 120 ], [ 10, 0, 120 ], [ 10, -14, 106 ] );
            drawShadow( [ -10, 0, 120 ], [ -10, 14, 106 ], [ 10, 14, 106 ], [ 10, 0, 120 ] );
            // other plates
            drawShadow( [ -100, -100, 20 ], [ -100, -100-d, 20+d ], [ 100, -100-d, 20+d ], [ 100, -100, 20 ] );
            drawShadow( [ -100, 100, 20 ], [ -100, 100+d, 20+d ], [ 100, 100+d, 20+d ], [ 100, 100, 20 ] );
          } else {
            // plate is in full shadow
            g.RectOnPlane( -100, -100, 100, 100, 3 );
          }
          g.SetAlpha( 1 );
          g.DeleteClipPlanes();

          // plate axis
          g.SetLineAttr( 'red', 2 );
          g.LineOnPlane( -100, 0, 100, 0 );
          g.SetLineAttr( 'blue', 2 );
          g.LineOnPlane( 0, -100, 0, 100 );

        // gonomon
        g.SetAreaAttr( 'white', 'black', 1 );
        g.SetPlane( [ 0, -100, 20 ], [ -1, 0, 0 ], [ 0, 1, 1 ], true );
        g.RectOnPlane( -30, 0, -10, 100 * sqrt2, 3 );
        g.RectOnPlane( -10, 90 * sqrt2, 10, 100 * sqrt2, 3 );
        g.RectOnPlane( 10, 0, 30, 100 * sqrt2, 3 );
        g.SetPlane( [ 0, 100, 20 ], [ -1, 0, 0 ], [ 0, -1, 1 ], true );
        g.RectOnPlane( -30, 0, -10, 100 * sqrt2, 3 );
        g.RectOnPlane( -10, 90 * sqrt2, 10, 100 * sqrt2, 3 );
        g.RectOnPlane( 10, 0, 30, 100 * sqrt2, 3 );

        // right plate
        g.SetPlane( [ 0, 100, 20 ], [ 1, 0, 0 ], [ 0, 1, 1 ], true );

          // plate background
          g.SetAreaAttr( 'white', 'black', 1 );
          g.RectOnPlane( -100, 0, 100, 100, 3 );

          // plate shadows
          addLocalClipPlane( [ -100, 100, 20 ], [ 0, 1, 0 ], [ 0, 0, 1 ] );
          addLocalClipPlane( [ 100, 100, 20 ], [ 0, 1, 0 ], [ 0, 0, -1 ] );
          addLocalClipPlane( [ 0, 100+100/sqrt2, 20+100/sqrt2 ], [ 1, 0, 0 ], [ 0, -1, 1 ] );
          addLocalClipPlane( [ 0, 100, 20 ], [ 1, 0, 0 ], [ 0, 1, -1 ] );
          g.SetAreaAttr( this.ShadowColor, this.ShadowColor, 1 );
          g.SetAlpha( this.ShadowAlpha );
          if (sunIsOnTopOfPlane()) {
            // gonomon
            drawShadow( [ -30, -100, 20 ], [ -30, 0, 120 ], [ -10, 0, 120 ], [ -10, -100, 20 ] );
            drawShadow( [ -30, 0, 120 ], [ -30, 100, 20 ], [ -10, 100, 20 ], [ -10, 0, 120 ] );
            drawShadow( [ 10, -100, 20 ], [ 10, 0, 120 ], [ 30, 0, 120 ], [ 30, -100, 20 ] );
            drawShadow( [ 10, 0, 120 ], [ 10, 100, 20 ], [ 30, 100, 20 ], [ 30, 0, 120 ] );
            // gonomon bar
            drawShadow( [ -10, -14, 106 ], [ -10, 0, 120 ], [ 10, 0, 120 ], [ 10, -14, 106 ] );
            drawShadow( [ -10, 0, 120 ], [ -10, 14, 106 ], [ 10, 14, 106 ], [ 10, 0, 120 ] );
            // other plates
            drawShadow( [ -100, -100, 20 ], [ -100, 100, 20 ], [ 100, 100, 20 ], [ 100, -100, 20 ] );
            drawShadow( [ -100, -100, 20 ], [ -100, -100-d, 20+d ], [ 100, -100-d, 20+d ], [ 100, -100, 20 ] );
          } else {
            // plate is in full shadow
            g.RectOnPlane( -100, 0, 100, 100, 3 );
          }
          g.SetAlpha( 1 );
          g.DeleteClipPlanes();

  },

};

Sundial.Create();

function UpdateAll() {
  Sundial.Update();
  ControlPanels.Update();
  Sundial.Draw();
}

xOnLoad( UpdateAll );

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

} ).AddValueSliderField( {
  Name: 'Time',
  Label: 'Local Time',
  ValueRef: 'Time',
  SliderValueRef: 'Time',
  Units: 'h',
  Color: 'red',
  Min: 6.0001,
  Max: 17.9999,
  Inc: 0.1,

} ).AddValueSliderField( {
  Name: 'Latitude',
  ValueRef: 'Latitude',
  SliderValueRef: 'Latitude',
  Units: '°',
  Color: 'black',
  Min: -90,
  Max: 90,
  Inc: 1,

} ).AddValueSliderField( {
  Name: 'Longitude',
  ValueRef: 'Longitude',
  SliderValueRef: 'Longitude',
  Units: '°',
  Color: 'black',
  Min: -180,
  Max: 180,
  Inc: 1,

} ).AddValueSliderField( {
  Name: 'EarthTilt',
  Label: 'Earth Tilt',
  ValueRef: 'EarthTilt',
  SliderValueRef: 'EarthTilt',
  Units: '°',
  Color: 'green',
  Min: -Sundial.MaxEarthTilt,
  Max: Sundial.MaxEarthTilt,
  Inc: 1,

} ).AddValueSliderField( {
  Name: 'Azimuth',
  ValueRef: 'Azimuth',
  SliderValueRef: 'Azimuth',
  Units: '°',
  Color: 'blue',
  Min: -10,
  Max: 10,
  Inc: 1,

} ).AddValueSliderField( {
  Name: 'DeltaAngle',
  Label: '&Delta;Angle',
  ValueRef: 'DeltaAngle',
  SliderValueRef: 'DeltaAngle',
  Units: '°',
  Color: 'blue',
  Min: -Sundial.MaxEarthTilt,
  Max: Sundial.MaxEarthTilt,
  Inc: 1,

} ).Render();

</jscript>

More Page Infos / Sitemap
Created Tuesday, September 25, 2018
Scroll to Top of Page
Changed Tuesday, September 25, 2018