@@ -34,7 +34,15 @@ Item {
3434 property color textColor: " #f3f4f5"
3535 property string fontFamily: " monospace"
3636
37+ property bool showTerminator: true
38+ property var subsolarPoint: RadioModel .subsolarPoint (new Date ())
39+ property color nightColor: " #040608"
40+ property real nightOpacity: 0.44
41+ property color terminatorColor: accentColor
42+ property real terminatorOpacity: 0.45
43+
3744 property var hoveredStation: null
45+
3846 property real hoverX: 0
3947 property real hoverY: 0
4048 property var highlightedStation: null
@@ -428,6 +436,90 @@ Item {
428436 }
429437 }
430438
439+ function paintSolarTerminator (ctx , centreX , centreY , globeRadius ) {
440+ if (! subsolarPoint) return
441+ var sLat = Number (subsolarPoint .latitude ) * Math .PI / 180
442+ var sLon = Number (subsolarPoint .longitude ) * Math .PI / 180
443+ var cosSLat = Math .cos (sLat)
444+ var sx = cosSLat * Math .cos (sLon)
445+ var sy = cosSLat * Math .sin (sLon)
446+ var sz = Math .sin (sLat)
447+
448+ var latitude = centreLatitude * Math .PI / 180
449+ var longitude = centreLongitude * Math .PI / 180
450+ var sinLatitude = Math .sin (latitude)
451+ var cosLatitude = Math .cos (latitude)
452+ var sinLongitude = Math .sin (longitude)
453+ var cosLongitude = Math .cos (longitude)
454+
455+ var horizontal = sx * cosLongitude + sy * sinLongitude
456+ var sCamX = sy * cosLongitude - sx * sinLongitude
457+ var sCamY = cosLatitude * sz - sinLatitude * horizontal
458+ var sCamZ = sinLatitude * sz + cosLatitude * horizontal
459+
460+ var inPlaneLen = Math .hypot (sCamX, sCamY)
461+
462+ if (inPlaneLen < 1e-4 ) {
463+ if (sCamZ < 0 ) {
464+ ctx .beginPath ()
465+ ctx .arc (centreX, centreY, globeRadius, 0 , Math .PI * 2 )
466+ ctx .fillStyle = withAlpha (nightColor, nightOpacity)
467+ ctx .fill ()
468+ }
469+ return
470+ }
471+
472+ var sunAngleMath = Math .atan2 (sCamY, sCamX)
473+ var sunAngleScreen = Math .atan2 (- sCamY, sCamX)
474+ var cosSun = Math .cos (sunAngleMath)
475+ var sinSun = Math .sin (sunAngleMath)
476+
477+ var steps = 64
478+ var ellipsePoints = []
479+ for (var i = 0 ; i <= steps; i++ ) {
480+ var t = - Math .PI / 2 + (i / steps) * Math .PI
481+ var v = Math .sin (t)
482+ var u = - sCamZ * Math .cos (t)
483+
484+ var x = u * cosSun - v * sinSun
485+ var y = u * sinSun + v * cosSun
486+
487+ var sxScreen = centreX + x * globeRadius
488+ var syScreen = centreY - y * globeRadius
489+ ellipsePoints .push ({ x: sxScreen, y: syScreen })
490+ }
491+
492+ var angleStartScreen = Math .atan2 (ellipsePoints[0 ].y - centreY, ellipsePoints[0 ].x - centreX)
493+ var angleEndScreen = Math .atan2 (ellipsePoints[steps].y - centreY, ellipsePoints[steps].x - centreX)
494+
495+ var awayFromSun = (sunAngleScreen + Math .PI * 3 ) % (Math .PI * 2 )
496+ var angleEndWrapped = (angleEndScreen + Math .PI * 2 ) % (Math .PI * 2 )
497+ var angleStartWrapped = (angleStartScreen + Math .PI * 2 ) % (Math .PI * 2 )
498+
499+ var diff = (angleStartWrapped - angleEndWrapped + Math .PI * 2 ) % (Math .PI * 2 )
500+ var diffSun = (awayFromSun - angleEndWrapped + Math .PI * 2 ) % (Math .PI * 2 )
501+ var anticlockwise = ! (diffSun < diff)
502+
503+ ctx .beginPath ()
504+ ctx .moveTo (ellipsePoints[0 ].x , ellipsePoints[0 ].y )
505+ for (var j = 1 ; j <= steps; j++ ) {
506+ ctx .lineTo (ellipsePoints[j].x , ellipsePoints[j].y )
507+ }
508+ ctx .arc (centreX, centreY, globeRadius, angleEndScreen, angleStartScreen, anticlockwise)
509+ ctx .closePath ()
510+ ctx .fillStyle = withAlpha (nightColor, nightOpacity)
511+ ctx .fill ()
512+
513+ ctx .beginPath ()
514+ ctx .moveTo (ellipsePoints[0 ].x , ellipsePoints[0 ].y )
515+ for (var k = 1 ; k <= steps; k++ ) {
516+ ctx .lineTo (ellipsePoints[k].x , ellipsePoints[k].y )
517+ }
518+ ctx .strokeStyle = withAlpha (terminatorColor, terminatorOpacity)
519+ ctx .lineWidth = Math .min (2.0 , Math .max (1.0 , globeRadius / 400 ))
520+ ctx .stroke ()
521+ }
522+
431523 function paintGlobe (ctx ) {
432524 var centreX = globeCanvas .width / 2
433525 var centreY = globeCanvas .height / 2
@@ -455,6 +547,7 @@ Item {
455547 ctx .clip ()
456548 paintGrid (ctx, centreX, centreY, globeRadius)
457549 paintCountries (ctx, centreX, centreY, globeRadius)
550+ if (showTerminator) paintSolarTerminator (ctx, centreX, centreY, globeRadius)
458551 paintSignals (ctx)
459552 ctx .restore ()
460553
@@ -543,6 +636,30 @@ Item {
543636 onVisibleChanged: {
544637 if (! visible) stopKineticRotation (true )
545638 }
639+ onSubsolarPointChanged: globeCanvas .requestPaint ()
640+ onShowTerminatorChanged: {
641+ if (showTerminator && visible) refreshSolarPosition ()
642+ globeCanvas .requestPaint ()
643+ }
644+ onNightColorChanged: globeCanvas .requestPaint ()
645+ onNightOpacityChanged: globeCanvas .requestPaint ()
646+ onTerminatorColorChanged: globeCanvas .requestPaint ()
647+ onTerminatorOpacityChanged: globeCanvas .requestPaint ()
648+
649+ function refreshSolarPosition () {
650+ root .subsolarPoint = RadioModel .subsolarPoint (new Date ())
651+ }
652+
653+ Timer {
654+ id: solarTimer
655+ interval: 60000
656+ running: root .visible && root .showTerminator
657+ repeat: true
658+ onRunningChanged: {
659+ if (running) root .refreshSolarPosition ()
660+ }
661+ onTriggered: root .refreshSolarPosition ()
662+ }
546663
547664 Canvas {
548665 id: globeCanvas
0 commit comments