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: 'ΔAngle',
ValueRef: 'DeltaAngle',
SliderValueRef: 'DeltaAngle',
Units: '°',
Color: 'blue',
Min: -Sundial.MaxEarthTilt,
Max: Sundial.MaxEarthTilt,
Inc: 1,
} ).Render();
</jscript>