Skip to content

Commit ab54c52

Browse files
committed
added day/night cycle and improved orbital lines
1 parent caa55ac commit ab54c52

7 files changed

Lines changed: 427 additions & 192 deletions

File tree

resources/data.tle

Lines changed: 235 additions & 139 deletions
Large diffs are not rendered by default.

resources/earth_night.png

3.13 MB
Loading

src/astro.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,42 @@ void get_apsis_2d(Satellite* sat, double current_time, bool is_apoapsis, double
223223
get_map_coordinates(pos3d, gmst_target, earth_offset, map_w, map_h, &out->x, &out->y);
224224
}
225225

226+
// i truly do hope this doesnt drift or something its so eyeballed istg
227+
Vector3 calculate_sun_position(double current_time_days) {
228+
current_time_days = normalize_epoch(current_time_days);
229+
230+
int yy = (int)(current_time_days / 1000.0);
231+
double day = fmod(current_time_days, 1000.0);
232+
233+
double unix_time = ConvertEpochYearAndDayToUnix(yy, day);
234+
double jd = (unix_time / 86400.0) + 2440587.5;
235+
double n = jd - 2451545.0;
236+
237+
double L = fmod(280.460 + 0.9856474 * n, 360.0);
238+
if (L < 0) L += 360.0;
239+
240+
double g = fmod(357.528 + 0.9856003 * n, 360.0);
241+
if (g < 0) g += 360.0;
242+
243+
double lambda = L + 1.915 * sin(g * DEG2RAD) + 0.020 * sin(2.0 * g * DEG2RAD);
244+
double epsilon = 23.439 - 0.0000004 * n;
245+
246+
double x_ecl = cos(lambda * DEG2RAD);
247+
double y_ecl = sin(lambda * DEG2RAD);
248+
double z_ecl = 0.0;
249+
250+
double x_eci = x_ecl;
251+
double y_eci = y_ecl * cos(epsilon * DEG2RAD) - z_ecl * sin(epsilon * DEG2RAD);
252+
double z_eci = y_ecl * sin(epsilon * DEG2RAD) + z_ecl * cos(epsilon * DEG2RAD);
253+
254+
Vector3 pos;
255+
pos.x = (float)(x_eci);
256+
pos.y = (float)(z_eci);
257+
pos.z = (float)(-y_eci);
258+
259+
return Vector3Normalize(pos);
260+
}
261+
226262
Vector3 calculate_moon_position(double current_time_days) {
227263
current_time_days = normalize_epoch(current_time_days);
228264

src/astro.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ void load_tle_data(const char* filename);
1010
double normalize_epoch(double epoch);
1111

1212
// orbit math stuff
13+
Vector3 calculate_sun_position(double current_time_days);
14+
void get_map_coordinates(Vector3 pos, double gmst_deg, float earth_offset, float map_w, float map_h, float* out_x, float* out_y);
1315
Vector3 calculate_position(Satellite* sat, double current_time_days);
1416
Vector3 calculate_moon_position(double current_time_days);
1517
void get_map_coordinates(Vector3 pos, double gmst_deg, float earth_offset, float map_w, float map_h, float* out_x, float* out_y);

src/config.c

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
#include <string.h>
44
#include <stdlib.h>
55

6-
bool show_clouds = false; // Global state
7-
86
// turn hex strings into real colors
97
Color ParseHexColor(const char* hexStr, Color fallback) {
108
if (!hexStr || hexStr[0] != '#') return fallback;
@@ -55,8 +53,24 @@ void LoadAppConfig(const char* filename, AppConfig* config) {
5553
char* comma = strchr(sc_ptr, ',');
5654
char* false_ptr = strstr(sc_ptr, "false");
5755
if (false_ptr && (!comma || false_ptr < comma)) {
58-
show_clouds = false;
56+
config->show_clouds = false;
57+
} else {
58+
config->show_clouds = true;
59+
}
60+
}
61+
62+
// parse the global night lights toggle (opt-out)
63+
char* snl_ptr = strstr(text, "\"show_night_lights\"");
64+
if (snl_ptr) {
65+
char* comma = strchr(snl_ptr, ',');
66+
char* false_ptr = strstr(snl_ptr, "false");
67+
if (false_ptr && (!comma || false_ptr < comma)) {
68+
config->show_night_lights = false;
69+
} else {
70+
config->show_night_lights = true;
5971
}
72+
} else {
73+
config->show_night_lights = true;
6074
}
6175

6276
// grabbing all the colors

src/main.c

Lines changed: 135 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,80 @@
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
1773
static 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

2279
static Font customFont;
23-
static Texture2D satIcon, markerIcon, earthTexture, moonTexture, cloudTexture;
80+
static Texture2D satIcon, markerIcon, earthTexture, moonTexture, cloudTexture, earthNightTexture;
2481
static Texture2D periMark, apoMark;
2582
static 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);

src/types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ typedef struct {
5151
float ui_scale;
5252
float earth_rotation_offset;
5353
float orbits_to_draw;
54+
bool show_clouds;
55+
bool show_night_lights;
5456

5557
Color bg_color;
5658
Color orbit_normal;

0 commit comments

Comments
 (0)