@@ -27,7 +27,15 @@ Item {
2727 property color textColor: " #f3f4f5"
2828 property string fontFamily: " monospace"
2929
30+ property bool showTerminator: true
31+ property var subsolarPoint: RadioModel .subsolarPoint (new Date ())
32+ property color nightColor: " #040608"
33+ property real nightOpacity: 0.44
34+ property color twilightColor: accentColor
35+ property real twilightOpacity: 0.45
36+
3037 property var hoveredStation: null
38+
3139 property real hoverX: 0
3240 property real hoverY: 0
3341 property var preparedCountries: []
@@ -328,6 +336,90 @@ Item {
328336 }
329337 }
330338
339+ function paintSolarTerminator (ctx , centreX , centreY , globeRadius ) {
340+ if (! subsolarPoint) return
341+ var sLat = Number (subsolarPoint .latitude ) * Math .PI / 180
342+ var sLon = Number (subsolarPoint .longitude ) * Math .PI / 180
343+ var cosSLat = Math .cos (sLat)
344+ var sx = cosSLat * Math .cos (sLon)
345+ var sy = cosSLat * Math .sin (sLon)
346+ var sz = Math .sin (sLat)
347+
348+ var latitude = centreLatitude * Math .PI / 180
349+ var longitude = centreLongitude * Math .PI / 180
350+ var sinLatitude = Math .sin (latitude)
351+ var cosLatitude = Math .cos (latitude)
352+ var sinLongitude = Math .sin (longitude)
353+ var cosLongitude = Math .cos (longitude)
354+
355+ var horizontal = sx * cosLongitude + sy * sinLongitude
356+ var sCamX = sy * cosLongitude - sx * sinLongitude
357+ var sCamY = cosLatitude * sz - sinLatitude * horizontal
358+ var sCamZ = sinLatitude * sz + cosLatitude * horizontal
359+
360+ var inPlaneLen = Math .hypot (sCamX, sCamY)
361+
362+ if (inPlaneLen < 1e-4 ) {
363+ if (sCamZ < 0 ) {
364+ ctx .beginPath ()
365+ ctx .arc (centreX, centreY, globeRadius, 0 , Math .PI * 2 )
366+ ctx .fillStyle = withAlpha (nightColor, nightOpacity)
367+ ctx .fill ()
368+ }
369+ return
370+ }
371+
372+ var sunAngleMath = Math .atan2 (sCamY, sCamX)
373+ var sunAngleScreen = Math .atan2 (- sCamY, sCamX)
374+ var cosSun = Math .cos (sunAngleMath)
375+ var sinSun = Math .sin (sunAngleMath)
376+
377+ var steps = 64
378+ var ellipsePoints = []
379+ for (var i = 0 ; i <= steps; i++ ) {
380+ var t = - Math .PI / 2 + (i / steps) * Math .PI
381+ var v = Math .sin (t)
382+ var u = - sCamZ * Math .cos (t)
383+
384+ var x = u * cosSun - v * sinSun
385+ var y = u * sinSun + v * cosSun
386+
387+ var sxScreen = centreX + x * globeRadius
388+ var syScreen = centreY - y * globeRadius
389+ ellipsePoints .push ({ x: sxScreen, y: syScreen })
390+ }
391+
392+ var angleStartScreen = Math .atan2 (ellipsePoints[0 ].y - centreY, ellipsePoints[0 ].x - centreX)
393+ var angleEndScreen = Math .atan2 (ellipsePoints[steps].y - centreY, ellipsePoints[steps].x - centreX)
394+
395+ var awayFromSun = (sunAngleScreen + Math .PI * 3 ) % (Math .PI * 2 )
396+ var angleEndWrapped = (angleEndScreen + Math .PI * 2 ) % (Math .PI * 2 )
397+ var angleStartWrapped = (angleStartScreen + Math .PI * 2 ) % (Math .PI * 2 )
398+
399+ var diff = (angleStartWrapped - angleEndWrapped + Math .PI * 2 ) % (Math .PI * 2 )
400+ var diffSun = (awayFromSun - angleEndWrapped + Math .PI * 2 ) % (Math .PI * 2 )
401+ var anticlockwise = ! (diffSun < diff)
402+
403+ ctx .beginPath ()
404+ ctx .moveTo (ellipsePoints[0 ].x , ellipsePoints[0 ].y )
405+ for (var j = 1 ; j <= steps; j++ ) {
406+ ctx .lineTo (ellipsePoints[j].x , ellipsePoints[j].y )
407+ }
408+ ctx .arc (centreX, centreY, globeRadius, angleEndScreen, angleStartScreen, anticlockwise)
409+ ctx .closePath ()
410+ ctx .fillStyle = withAlpha (nightColor, nightOpacity)
411+ ctx .fill ()
412+
413+ ctx .beginPath ()
414+ ctx .moveTo (ellipsePoints[0 ].x , ellipsePoints[0 ].y )
415+ for (var k = 1 ; k <= steps; k++ ) {
416+ ctx .lineTo (ellipsePoints[k].x , ellipsePoints[k].y )
417+ }
418+ ctx .strokeStyle = withAlpha (twilightColor, twilightOpacity)
419+ ctx .lineWidth = Math .min (2.0 , Math .max (1.0 , globeRadius / 400 ))
420+ ctx .stroke ()
421+ }
422+
331423 function paintGlobe (ctx ) {
332424 var centreX = globeCanvas .width / 2
333425 var centreY = globeCanvas .height / 2
@@ -355,6 +447,7 @@ Item {
355447 ctx .clip ()
356448 paintGrid (ctx, centreX, centreY, globeRadius)
357449 paintCountries (ctx, centreX, centreY, globeRadius)
450+ if (showTerminator) paintSolarTerminator (ctx, centreX, centreY, globeRadius)
358451 paintSignals (ctx)
359452 ctx .restore ()
360453
@@ -416,6 +509,20 @@ Item {
416509 onGlobeScaleChanged: globeCanvas .requestPaint ()
417510 onWidthChanged: globeCanvas .requestPaint ()
418511 onHeightChanged: globeCanvas .requestPaint ()
512+ onSubsolarPointChanged: globeCanvas .requestPaint ()
513+ onShowTerminatorChanged: globeCanvas .requestPaint ()
514+ onNightColorChanged: globeCanvas .requestPaint ()
515+ onTwilightColorChanged: globeCanvas .requestPaint ()
516+
517+ Timer {
518+ id: solarTimer
519+ interval: 60000
520+ running: true
521+ repeat: true
522+ onTriggered: {
523+ root .subsolarPoint = RadioModel .subsolarPoint (new Date ())
524+ }
525+ }
419526
420527 Canvas {
421528 id: globeCanvas
0 commit comments