44#include <stdlib.h>
55#include <math.h>
66
7- // #include "rlgl.h"
8-
97#define RAYGUI_IMPLEMENTATION
108#include "raygui.h"
119
1210#include "types.h"
1311#include "config.h"
1412#include "astro.h"
1513
14+ // GLSL 330 fragment shader magic to blend day and night textures based on the sun's position relative
15+ const char * fs3D =
16+ "#version 330\n"
17+ "in vec2 fragTexCoord;\n"
18+ "in vec4 fragColor;\n"
19+ "out vec4 finalColor;\n"
20+ "uniform sampler2D texture0;\n"
21+ "uniform sampler2D texture1;\n"
22+ "uniform vec3 sunDir;\n"
23+ "void main() {\n"
24+ " vec4 day = texture(texture0, fragTexCoord);\n"
25+ " vec4 night = texture(texture1, fragTexCoord);\n"
26+ " float theta = (fragTexCoord.x - 0.5) * 6.28318530718;\n"
27+ " float phi = fragTexCoord.y * 3.14159265359;\n"
28+ " vec3 normal = vec3(cos(theta)*sin(phi), cos(phi), -sin(theta)*sin(phi));\n"
29+ " float intensity = dot(normal, sunDir);\n"
30+ " float blend = smoothstep(-0.15, 0.15, intensity);\n"
31+ " finalColor = mix(night, day, blend) * fragColor;\n"
32+ "}\n" ;
33+
34+ const char * fs2D =
35+ "#version 330\n"
36+ "in vec2 fragTexCoord;\n"
37+ "in vec4 fragColor;\n"
38+ "out vec4 finalColor;\n"
39+ "uniform sampler2D texture0;\n"
40+ "uniform sampler2D texture1;\n"
41+ "uniform vec3 sunDir;\n"
42+ "void main() {\n"
43+ " vec4 day = texture(texture0, fragTexCoord);\n"
44+ " vec4 night = texture(texture1, fragTexCoord);\n"
45+ " float theta = (fragTexCoord.x - 0.5) * 6.28318530718;\n"
46+ " float phi = fragTexCoord.y * 3.14159265359;\n"
47+ " vec3 normal = vec3(cos(theta)*sin(phi), cos(phi), -sin(theta)*sin(phi));\n"
48+ " float intensity = dot(normal, sunDir);\n"
49+ " float blend = smoothstep(-0.15, 0.15, intensity);\n"
50+ " finalColor = mix(night, day, blend) * fragColor;\n"
51+ "}\n" ;
52+
53+ const char * fsCloud3D =
54+ "#version 330\n"
55+ "in vec2 fragTexCoord;\n"
56+ "in vec4 fragColor;\n"
57+ "out vec4 finalColor;\n"
58+ "uniform sampler2D texture0;\n" // cloud map
59+ "uniform vec3 sunDir;\n"
60+ "void main() {\n"
61+ " vec4 texel = texture(texture0, fragTexCoord);\n"
62+ " float theta = (fragTexCoord.x - 0.5) * 6.28318530718;\n"
63+ " float phi = fragTexCoord.y * 3.14159265359;\n"
64+ " vec3 normal = vec3(cos(theta)*sin(phi), cos(phi), -sin(theta)*sin(phi));\n"
65+ " float intensity = dot(normal, sunDir);\n"
66+ " \n"
67+ " // Fade clouds to transparent on the night side \n"
68+ " float alpha = smoothstep(-0.15, 0.05, intensity);\n"
69+ " finalColor = vec4(texel.rgb, texel.a * alpha) * fragColor;\n"
70+ "}\n" ;
71+
1672// defaults
1773static AppConfig cfg = {
1874 .window_width = 1280 , .window_height = 720 , .target_fps = 120 , .ui_scale = 1.0f ,
75+ .show_clouds = false, .show_night_lights = true,
1976 .bg_color = {0 , 0 , 0 , 255 }, .text_main = {255 , 255 , 255 , 255 }
2077};
2178
2279static Font customFont ;
23- static Texture2D satIcon , markerIcon , earthTexture , moonTexture , cloudTexture ;
80+ static Texture2D satIcon , markerIcon , earthTexture , moonTexture , cloudTexture , earthNightTexture ;
2481static Texture2D periMark , apoMark ;
2582static Model earthModel , moonModel , cloudModel ;
2683
@@ -105,27 +162,14 @@ static void draw_orbit_3d(Satellite* sat, double current_epoch, bool is_highligh
105162 Color orbitColor = is_highlighted ? cfg .orbit_highlighted : cfg .orbit_normal ;
106163 orbitColor = ApplyAlpha (orbitColor , alpha );
107164
108- double delta_time_s_curr = (current_epoch - sat -> epoch_days ) * 86400.0 ;
109- double M_curr = fmod (sat -> mean_anomaly + sat -> mean_motion * delta_time_s_curr , 2.0 * PI );
110- if (M_curr < 0 ) M_curr += 2.0 * PI ;
111-
112- double E_curr = M_curr ;
113- for (int k = 0 ; k < 10 ; k ++ ) {
114- double delta = (E_curr - sat -> eccentricity * sin (E_curr ) - M_curr ) / (1.0 - sat -> eccentricity * cos (E_curr ));
115- E_curr -= delta ;
116- if (fabs (delta ) < 1e-6 ) break ;
117- }
118-
119165 double orbits_count = is_highlighted ? (double )cfg .orbits_to_draw : 1.0 ;
166+
167+ // period calculation
168+ double period_days = (2.0 * PI / sat -> mean_motion ) / 86400.0 ;
169+ double time_step = (period_days * orbits_count ) / segments ;
120170
121171 for (int i = 0 ; i <= segments ; i ++ ) {
122- double progress = (double )i / segments ;
123- double E_target = E_curr + (progress * 2.0 * PI * orbits_count );
124-
125- double M_target = E_target - sat -> eccentricity * sin (E_target );
126- double delta_M = M_target - M_curr ;
127- double t = current_epoch + (delta_M / sat -> mean_motion ) / 86400.0 ;
128-
172+ double t = current_epoch + (i * time_step );
129173 Vector3 pos = Vector3Scale (calculate_position (sat , t ), 1.0f / DRAW_SCALE );
130174
131175 if (i > 0 ) DrawLine3D (prev_pos , pos , orbitColor );
@@ -163,8 +207,6 @@ int main(void) {
163207
164208 load_tle_data ("resources/data.tle" );
165209
166- // rlSetClipPlanes(0.1, 50000.0); // magic fix to get rid of far plane clipping
167-
168210 // set up the cameras for both views
169211 Camera camera3d = { 0 };
170212 camera3d .target = (Vector3 ){ 0.0f , 0.0f , 0.0f };
@@ -186,13 +228,31 @@ int main(void) {
186228 earthModel = LoadModelFromMesh (sphereMesh );
187229 earthTexture = LoadTexture ("resources/earth.png" );
188230 earthModel .materials [0 ].maps [MATERIAL_MAP_DIFFUSE ].texture = earthTexture ;
231+ earthNightTexture = LoadTexture ("resources/earth_night.png" );
232+
233+ Shader shader3D = LoadShaderFromMemory (NULL , fs3D );
234+ int sunDirLoc3D = GetShaderLocation (shader3D , "sunDir" );
235+
236+ // link texture1 to raylib's EMISSION material map
237+ shader3D .locs [SHADER_LOC_MAP_EMISSION ] = GetShaderLocation (shader3D , "texture1" );
238+ earthModel .materials [0 ].maps [MATERIAL_MAP_EMISSION ].texture = earthNightTexture ;
239+
240+ Shader defaultEarthShader = earthModel .materials [0 ].shader ; // cache the default shader
241+
242+ Shader shader2D = LoadShaderFromMemory (NULL , fs2D );
243+ int sunDirLoc2D = GetShaderLocation (shader2D , "sunDir" );
244+ int nightTexLoc2D = GetShaderLocation (shader2D , "texture1" );
189245
190246 float draw_cloud_radius = (EARTH_RADIUS_KM + 25.0f ) / DRAW_SCALE ;
191247 Mesh cloudMesh = GenEarthMesh (draw_cloud_radius , 64 , 64 );
192248 cloudModel = LoadModelFromMesh (cloudMesh );
193249 cloudTexture = LoadTexture ("resources/clouds.png" );
194250 cloudModel .materials [0 ].maps [MATERIAL_MAP_DIFFUSE ].texture = cloudTexture ;
195251
252+ Shader shaderCloud = LoadShaderFromMemory (NULL , fsCloud3D );
253+ int sunDirLocCloud = GetShaderLocation (shaderCloud , "sunDir" );
254+ Shader defaultCloudShader = cloudModel .materials [0 ].shader ;
255+
196256 float draw_moon_radius = MOON_RADIUS_KM / DRAW_SCALE ;
197257 Mesh moonMesh = GenEarthMesh (draw_moon_radius , 32 , 32 );
198258 moonModel = LoadModelFromMesh (moonMesh );
@@ -296,7 +356,7 @@ int main(void) {
296356
297357 for (int i = 0 ; i < sat_count ; i ++ ) {
298358 satellites [i ].current_pos = calculate_position (& satellites [i ], current_epoch );
299- // prevent raycasting on hidden satellites satellites
359+ // prevent raycasting on hidden satellites
300360 if (hide_unselected && selected_sat != NULL && & satellites [i ] != selected_sat ) continue ;
301361
302362 float mx , my ;
@@ -347,7 +407,7 @@ int main(void) {
347407 }
348408
349409 // defining a hitbox area to cover the UI checkboxes + texts
350- Rectangle cbHitbox = { 10 * cfg .ui_scale , (10 + 104 ) * cfg .ui_scale , 200 * cfg .ui_scale , 50 * cfg .ui_scale };
410+ Rectangle cbHitbox = { 10 * cfg .ui_scale , (10 + 104 ) * cfg .ui_scale , 200 * cfg .ui_scale , 74 * cfg .ui_scale };
351411
352412 if (IsMouseButtonPressed (MOUSE_BUTTON_LEFT )) {
353413 // only update selection if the mouse is not clicking on the UI Checkboxes
@@ -449,9 +509,24 @@ int main(void) {
449509 if (is_2d_view ) {
450510 // drawing the flat map
451511 BeginMode2D (camera2d );
512+ if (cfg .show_night_lights ) {
513+ BeginShaderMode (shader2D );
514+ SetShaderValueTexture (shader2D , nightTexLoc2D , earthNightTexture );
515+
516+ Vector3 sunEci = calculate_sun_position (current_epoch );
517+ float earth_rot_rad = (gmst_deg + cfg .earth_rotation_offset ) * DEG2RAD ;
518+ // rotate Sun vector by -GMST to get ECEF mapping for 2d
519+ Vector3 sunEcef = Vector3Transform (sunEci , MatrixRotateY (- earth_rot_rad ));
520+ SetShaderValue (shader2D , sunDirLoc2D , & sunEcef , SHADER_UNIFORM_VEC3 );
521+ }
522+
452523 DrawTexturePro (earthTexture , (Rectangle ){0 , 0 , earthTexture .width , earthTexture .height },
453524 (Rectangle ){- map_w /2 , - map_h /2 , map_w , map_h }, (Vector2 ){0 ,0 }, 0.0f , WHITE );
454525
526+ if (cfg .show_night_lights ) {
527+ EndShaderMode ();
528+ }
529+
455530 Vector2 mapMin = GetWorldToScreen2D ((Vector2 ){- map_w /2.0f , - map_h /2.0f }, camera2d );
456531 Vector2 mapMax = GetWorldToScreen2D ((Vector2 ){map_w /2.0f , map_h /2.0f }, camera2d );
457532
@@ -514,28 +589,11 @@ int main(void) {
514589 int segments = fmin (4000 , fmax (50 , (int )(400 * cfg .orbits_to_draw )));
515590 Vector2 track_pts [4001 ]; // must be segments + 1
516591
517- // grab the current mean and eccentric anomaly to start the track from the exact current position
518- double delta_time_s_curr = (current_epoch - satellites [i ].epoch_days ) * 86400.0 ;
519- double M_curr = fmod (satellites [i ].mean_anomaly + satellites [i ].mean_motion * delta_time_s_curr , 2.0 * PI );
520- if (M_curr < 0 ) M_curr += 2.0 * PI ;
521-
522- double E_curr = M_curr ;
523- for (int k = 0 ; k < 10 ; k ++ ) {
524- double delta = (E_curr - satellites [i ].eccentricity * sin (E_curr ) - M_curr ) / (1.0 - satellites [i ].eccentricity * cos (E_curr ));
525- E_curr -= delta ;
526- if (fabs (delta ) < 1e-6 ) break ;
527- }
592+ double period_days = (2.0 * PI / satellites [i ].mean_motion ) / 86400.0 ;
593+ double time_step = (period_days * cfg .orbits_to_draw ) / segments ;
528594
529- // step through eccentric anomaly instead of time for smoother curves at periapsis
530595 for (int j = 0 ; j <= segments ; j ++ ) {
531- double progress = (double )j / segments ;
532- double E_target = E_curr + (progress * 2.0 * PI * cfg .orbits_to_draw );
533-
534- // work backward to mean anomaly and then to time
535- double M_target = E_target - satellites [i ].eccentricity * sin (E_target );
536- double delta_M = M_target - M_curr ;
537- double t = current_epoch + (delta_M / satellites [i ].mean_motion ) / 86400.0 ;
538-
596+ double t = current_epoch + (j * time_step );
539597 get_map_coordinates (calculate_position (& satellites [i ], t ), epoch_to_gmst (t ), cfg .earth_rotation_offset , map_w , map_h , & track_pts [j ].x , & track_pts [j ].y );
540598 }
541599
@@ -589,14 +647,34 @@ int main(void) {
589647 // drawing the 3d view
590648 BeginMode3D (camera3d );
591649 earthModel .transform = MatrixRotateY ((gmst_deg + cfg .earth_rotation_offset ) * DEG2RAD );
650+
651+ if (cfg .show_night_lights ) {
652+ earthModel .materials [0 ].shader = shader3D ;
653+ Vector3 sunEci = calculate_sun_position (current_epoch );
654+ float earth_rot_rad = (gmst_deg + cfg .earth_rotation_offset ) * DEG2RAD ;
655+ Vector3 sunEcef = Vector3Transform (sunEci , MatrixRotateY (- earth_rot_rad ));
656+ SetShaderValue (shader3D , sunDirLoc3D , & sunEcef , SHADER_UNIFORM_VEC3 );
657+ } else {
658+ earthModel .materials [0 ].shader = defaultEarthShader ;
659+ }
660+
592661 DrawModel (earthModel , Vector3Zero (), 1.0f , WHITE );
593662
594- if (show_clouds ) {
663+ if (cfg . show_clouds ) {
595664 double continuous_cloud_angle = fmod (gmst_deg + cfg .earth_rotation_offset + (current_epoch * 360.0 * 0.04 ), 360.0 );
596- cloudModel .transform = MatrixRotateY ((float )(continuous_cloud_angle * DEG2RAD ));
597- // BeginBlendMode(BLEND_ADDITIVE); // garbage tint, dont use it
665+ float cloud_rot_rad = (float )(continuous_cloud_angle * DEG2RAD );
666+ cloudModel .transform = MatrixRotateY (cloud_rot_rad );
667+
668+ if (cfg .show_night_lights ) {
669+ cloudModel .materials [0 ].shader = shaderCloud ;
670+ Vector3 sunEci = calculate_sun_position (current_epoch );
671+ Vector3 sunCloudSpace = Vector3Transform (sunEci , MatrixRotateY (- cloud_rot_rad ));
672+ SetShaderValue (shaderCloud , sunDirLocCloud , & sunCloudSpace , SHADER_UNIFORM_VEC3 );
673+ } else {
674+ cloudModel .materials [0 ].shader = defaultCloudShader ;
675+ }
676+
598677 DrawModel (cloudModel , Vector3Zero (), 1.0f , WHITE );
599- EndBlendMode ();
600678 }
601679
602680 // render Moon (not sure what for tbh but I'm sure someone will appreciate it :3)
@@ -723,7 +801,10 @@ int main(void) {
723801 GuiCheckBox (cbRec , "Hide Unselected" , & hide_unselected );
724802
725803 Rectangle cbRec2 = { 10 * cfg .ui_scale , (10 + 128 ) * cfg .ui_scale , 20 * cfg .ui_scale , 20 * cfg .ui_scale };
726- GuiCheckBox (cbRec2 , "Show Clouds" , & show_clouds );
804+ GuiCheckBox (cbRec2 , "Show Clouds" , & cfg .show_clouds );
805+
806+ Rectangle cbRec3 = { 10 * cfg .ui_scale , (10 + 152 ) * cfg .ui_scale , 20 * cfg .ui_scale , 20 * cfg .ui_scale };
807+ GuiCheckBox (cbRec3 , "Night Lights" , & cfg .show_night_lights );
727808
728809 if (active_sat ) {
729810 Vector2 screenPos ;
@@ -828,7 +909,11 @@ int main(void) {
828909 UnloadTexture (periMark );
829910 UnloadTexture (apoMark );
830911 UnloadTexture (earthTexture );
912+ UnloadTexture (earthNightTexture );
831913 UnloadModel (earthModel );
914+ UnloadShader (shader3D );
915+ UnloadShader (shader2D );
916+ UnloadShader (shaderCloud );
832917 UnloadTexture (cloudTexture );
833918 UnloadModel (cloudModel );
834919 UnloadTexture (moonTexture );
0 commit comments