Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save pawel-dubiel/dcfef86130dd338a1d0ed4dd2c6ed2ef to your computer and use it in GitHub Desktop.

Select an option

Save pawel-dubiel/dcfef86130dd338a1d0ed4dd2c6ed2ef to your computer and use it in GitHub Desktop.
/**
C Implementation (cland) Instructions
Setup
Install SDL2 Development Libraries:
* macOS (Homebrew):
brew install sdl2
* Linux (apt-get):
sudo apt-get install libsdl2-dev
* Windows (MSYS2/vcpkg): Installation varies, but you'll need the SDL2 development headers and libraries.
Navigate to the project directory:
Compile the project:
- make
This will create an executable named `galaxy` in the `cland` directory.
Running
Basic Run:
./galaxy
Force Star System Type (1 for single, 2 for binary, 3 for trinary):
./galaxy --stars=2
Controls
Mouse Left-Click (Galaxy View): Enter a star system.
Mouse Left-Click (System View): Select a star or planet to view details.
Mouse Double Left-Click (System View): Center the camera on the selected planet or star.
Mouse Right-Click (System View): Exit the current star system and return to the Galaxy Map.
Arrow Keys (Both Views): Pan the camera.
Mouse Scroll Wheel (Both Views): Zoom in and out.
`UP` / `DOWN` (System View): Speed up / Slow down time.
`SPACE` (System View): Pause / Resume the simulation.
`+` / `=` (Both Views): Zoom in.
`-` (Both Views): Zoom out.
*/
--- cland/font.h ---
#ifndef FONT_H
#define FONT_H
#include <SDL2/SDL.h>
// Simple 5x7 font data (ASC 32-127)
static const unsigned char font_data[] = {
0x00,0x00,0x00,0x00,0x00, // space
0x00,0x00,0x5F,0x00,0x00, // !
0x00,0x07,0x00,0x07,0x00, // "
0x14,0x7F,0x14,0x7F,0x14, // #
0x24,0x2A,0x7F,0x2A,0x12, // $
0x23,0x13,0x08,0x64,0x62, // %
0x36,0x49,0x55,0x22,0x50, // &
0x00,0x05,0x03,0x00,0x00, // '
0x00,0x1C,0x22,0x41,0x00, // (
0x00,0x41,0x22,0x1C,0x00, // )
0x14,0x08,0x3E,0x08,0x14, // *
0x08,0x08,0x3E,0x08,0x08, // +
0x00,0x50,0x30,0x00,0x00, // ,
0x08,0x08,0x08,0x08,0x08, // -
0x00,0x60,0x60,0x00,0x00, // .
0x20,0x10,0x08,0x04,0x02, // /
0x3E,0x51,0x49,0x45,0x3E, // 0
0x00,0x42,0x7F,0x40,0x00, // 1
0x42,0x61,0x51,0x49,0x46, // 2
0x21,0x41,0x45,0x4B,0x31, // 3
0x18,0x14,0x12,0x7F,0x10, // 4
0x27,0x45,0x45,0x45,0x39, // 5
0x3C,0x4A,0x49,0x49,0x30, // 6
0x01,0x71,0x09,0x05,0x03, // 7
0x36,0x49,0x49,0x49,0x36, // 8
0x06,0x49,0x49,0x29,0x1E, // 9
0x00,0x36,0x36,0x00,0x00, // :
0x00,0x56,0x36,0x00,0x00, // ;
0x08,0x14,0x22,0x41,0x00, // <
0x14,0x14,0x14,0x14,0x14, // =
0x00,0x41,0x22,0x14,0x08, // >
0x02,0x01,0x51,0x09,0x06, // ?
0x32,0x49,0x79,0x41,0x3E, // @
0x7E,0x11,0x11,0x11,0x7E, // A
0x7F,0x49,0x49,0x49,0x36, // B
0x3E,0x41,0x41,0x41,0x22, // C
0x7F,0x41,0x41,0x22,0x1C, // D
0x7F,0x49,0x49,0x49,0x41, // E
0x7F,0x09,0x09,0x09,0x01, // F
0x3E,0x41,0x49,0x49,0x7A, // G
0x7F,0x08,0x08,0x08,0x7F, // H
0x00,0x41,0x7F,0x41,0x00, // I
0x20,0x40,0x41,0x3F,0x01, // J
0x7F,0x08,0x14,0x22,0x41, // K
0x7F,0x40,0x40,0x40,0x40, // L
0x7F,0x02,0x0C,0x02,0x7F, // M
0x7F,0x04,0x08,0x10,0x7F, // N
0x3E,0x41,0x41,0x41,0x3E, // O
0x7F,0x09,0x09,0x09,0x06, // P
0x3E,0x41,0x51,0x21,0x5E, // Q
0x7F,0x09,0x19,0x29,0x46, // R
0x46,0x49,0x49,0x49,0x31, // S
0x01,0x01,0x7F,0x01,0x01, // T
0x3F,0x40,0x40,0x40,0x3F, // U
0x1F,0x20,0x40,0x20,0x1F, // V
0x3F,0x40,0x38,0x40,0x3F, // W
0x63,0x14,0x08,0x14,0x63, // X
0x07,0x08,0x70,0x08,0x07, // Y
0x61,0x51,0x49,0x45,0x43, // Z
0x00,0x7F,0x41,0x41,0x00, // [
0x02,0x04,0x08,0x10,0x20, // backslash
0x00,0x41,0x41,0x7F,0x00, // ]
0x04,0x02,0x01,0x02,0x04, // ^
0x40,0x40,0x40,0x40,0x40, // _
0x00,0x01,0x02,0x04,0x00, // `
0x20,0x54,0x54,0x54,0x78, // a
0x7F,0x48,0x44,0x44,0x38, // b
0x38,0x44,0x44,0x44,0x20, // c
0x38,0x44,0x44,0x48,0x7F, // d
0x38,0x54,0x54,0x54,0x18, // e
0x08,0x7E,0x09,0x01,0x02, // f
0x0C,0x52,0x52,0x52,0x3E, // g
0x7F,0x08,0x04,0x04,0x78, // h
0x00,0x44,0x7D,0x40,0x00, // i
0x20,0x40,0x44,0x3D,0x00, // j
0x7F,0x10,0x28,0x44,0x00, // k
0x00,0x41,0x7F,0x40,0x00, // l
0x7C,0x04,0x18,0x04,0x78, // m
0x7C,0x08,0x04,0x04,0x78, // n
0x38,0x44,0x44,0x44,0x38, // o
0x7C,0x14,0x14,0x14,0x08, // p
0x08,0x14,0x14,0x18,0x7C, // q
0x7C,0x08,0x04,0x04,0x08, // r
0x48,0x54,0x54,0x54,0x20, // s
0x04,0x3F,0x44,0x40,0x20, // t
0x3C,0x40,0x40,0x20,0x7C, // u
0x1C,0x20,0x40,0x20,0x1C, // v
0x3C,0x40,0x30,0x40,0x3C, // w
0x44,0x28,0x10,0x28,0x44, // x
0x0C,0x50,0x50,0x50,0x3C, // y
0x44,0x64,0x54,0x4C,0x44, // z
0x00,0x08,0x36,0x41,0x00, // {
0x00,0x00,0x7F,0x00,0x00, // |
0x00,0x41,0x36,0x08,0x00, // }
0x10,0x08,0x08,0x10,0x08, // ~
};
void draw_char(SDL_Renderer *renderer, int x, int y, char c, int scale) {
if (c < 32 || c > 126) c = '?';
const unsigned char *glyph = &font_data[(c - 32) * 5];
for (int i = 0; i < 5; i++) {
unsigned char line = glyph[i];
for (int j = 0; j < 7; j++) {
if (line & (1 << j)) {
if (scale == 1) {
SDL_RenderDrawPoint(renderer, x + i, y + j);
} else {
SDL_Rect rect = {x + i * scale, y + j * scale, scale, scale};
SDL_RenderFillRect(renderer, &rect);
}
}
}
}
}
void draw_text(SDL_Renderer *renderer, int x, int y, const char *text, int scale) {
while (*text) {
draw_char(renderer, x, y, *text, scale);
x += 6 * scale;
text++;
}
}
#endif
--- cland/galaxy.h ---
#ifndef GALAXY_H
#define GALAXY_H
#include <SDL2/SDL.h>
#include <stdbool.h>
#define SCREEN_WIDTH 1200
#define SCREEN_HEIGHT 900
#define MAX(x, y) (((x) > (y)) ? (x) : (y)) // Shared macro
// Math
typedef struct {
float x, y;
} Vec2;
// Data Structures
typedef struct {
char name[32];
float mass;
float radius;
float temp;
float luminosity;
float metallicity;
Uint32 color;
// Physics
float x, y; // AU
float vx, vy; // AU/year
} Star;
typedef enum {
TYPE_ROCKY,
TYPE_TERRESTRIAL,
TYPE_GAS_GIANT,
TYPE_ICE_GIANT,
TYPE_MOLTEN,
TYPE_ICY_WORLD
} PlanetType;
typedef struct {
char name[32];
float radius;
float orbit_dist; // Relative visual distance
float x, y; // Angle/Speed in simulation or actual coords
float vx, vy;
} Moon;
typedef struct {
char name[32];
float mass;
float radius;
Uint32 color;
PlanetType type;
// Physics
float x, y; // AU
float vx, vy; // AU/year
// Details
float density;
float surface_temp;
char atmosphere[32];
float albedo;
float greenhouse;
bool is_tidally_locked;
float gravity;
float rotation_period;
float rotation_angle; // Current axial rotation
// Resources (Percentages 0.0 - 100.0)
// 0:Iron, 1:Silicon, 2:Nickel, 3:Water, 4:Hydrogen, 5:Helium, 6:Gold, 7:RareEarths
float resources[8];
int moon_count;
Moon moons[4];
} Planet;
typedef struct {
int x, y;
unsigned int seed;
int star_count;
Star stars[3];
int planet_count;
Planet planets[16];
} StarSystem;
// Game State
typedef enum {
VIEW_GALAXY,
VIEW_SYSTEM
} ViewMode;
typedef struct {
ViewMode mode;
Vec2 camera_pos; // Current camera position (System or Galaxy)
Vec2 galaxy_camera_pos; // Saved galaxy position
float zoom;
float galaxy_zoom; // Saved galaxy zoom
StarSystem current_system;
bool running;
bool debug;
bool paused;
float time_speed;
int selected_planet_idx; // -1 for none
bool selected_star;
int centered_planet_idx; // -1 for none (Star centered)
Vec2 system_center_offset; // Offset to center view on a body
// Cache
StarSystem *visited_systems;
int visited_count;
int visited_capacity;
} GameState;
// Functions
unsigned int get_seed(int x, int y);
bool star_exists(int x, int y);
StarSystem generate_system(int x, int y);
float rand_float(float min, float max);
void set_forced_star_count(int count); // New
#endif
--- cland/gen.c ---
#include "galaxy.h"
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
// Deterministic Hash
unsigned int get_seed(int x, int y) {
unsigned int seed = (x * 73856093) ^ (y * 19349663);
return seed;
}
bool star_exists(int x, int y) {
unsigned int seed = get_seed(x, y);
srand(seed);
return (rand() % 100) < 5;
}
float rand_float(float min, float max) {
return min + (float)rand() / (float)(RAND_MAX / (max - min));
}
// Helper
static int g_forced_star_count = 0;
void set_forced_star_count(int count) {
g_forced_star_count = count;
}
void generate_resources(Planet *p, float metallicity) {
// 0:Fe, 1:Si, 2:Ni, 3:H2O, 4:H, 5:He, 6:Au, 7:REE
for(int i=0; i<8; i++) p->resources[i] = 0.0f;
float metal_boost = powf(10.0, metallicity);
float weights[8] = {0};
if (p->type == TYPE_GAS_GIANT) {
weights[4] = 75.0;
weights[5] = 24.0;
weights[3] = 1.0 * metal_boost;
} else if (p->type == TYPE_ICE_GIANT) {
weights[3] = 50.0;
weights[4] = 15.0;
weights[5] = 5.0;
weights[1] = 5.0;
} else if (p->type == TYPE_MOLTEN) {
weights[0] = 40.0;
weights[1] = 30.0;
weights[2] = 10.0;
weights[6] = 0.1 * metal_boost;
} else if (p->type == TYPE_TERRESTRIAL || p->type == TYPE_ROCKY) {
weights[1] = 30.0;
weights[0] = 25.0;
weights[3] = (p->type == TYPE_TERRESTRIAL) ? 5.0 : 0.1;
weights[2] = 5.0;
weights[7] = 0.05 * metal_boost;
weights[6] = 0.01 * metal_boost;
} else { // Icy
weights[3] = 70.0;
weights[1] = 10.0;
}
float total = 0;
for(int i=0; i<8; i++) {
if (weights[i] > 0) weights[i] *= rand_float(0.8, 1.2);
total += weights[i];
}
if (total > 0) {
for(int i=0; i<8; i++) p->resources[i] = (weights[i] / total) * 100.0;
}
}
StarSystem generate_system(int x, int y) {
StarSystem sys;
sys.x = x;
sys.y = y;
sys.seed = get_seed(x, y);
srand(sys.seed);
if (g_forced_star_count > 0) sys.star_count = g_forced_star_count;
else {
int r = rand() % 100;
sys.star_count = (r < 60) ? 1 : ((r < 90) ? 2 : 3);
}
float total_mass = 0;
float total_lum = 0;
for (int i = 0; i < sys.star_count; i++) {
Star *s = &sys.stars[i];
sprintf(s->name, "Star-%c", 'A' + i);
s->mass = rand_float(0.1, 5.0);
if (i > 0) s->mass *= 0.5;
s->radius = powf(s->mass, 0.8);
s->luminosity = powf(s->mass, 3.5);
s->metallicity = rand_float(-0.5, 0.5);
s->temp = 5778 * powf(s->mass, 0.5);
if (s->temp > 10000) s->color = 0x8888FFFF;
else if (s->temp > 6000) s->color = 0xFFFFFFFF;
else if (s->temp > 4000) s->color = 0xFFFF00FF;
else s->color = 0xFF4400FF;
total_mass += s->mass;
total_lum += s->luminosity;
}
if (sys.star_count == 1) {
sys.stars[0].x = 0; sys.stars[0].y = 0;
sys.stars[0].vx = 0; sys.stars[0].vy = 0;
} else if (sys.star_count == 2) {
Star *s1 = &sys.stars[0]; Star *s2 = &sys.stars[1];
float dist = 0.2;
float v_circ = sqrtf(39.478 * (s1->mass + s2->mass) / dist);
float r1 = dist * s2->mass / (s1->mass + s2->mass);
float r2 = dist * s1->mass / (s1->mass + s2->mass);
s1->x = -r1; s1->y = 0; s1->vx = 0; s1->vy = v_circ * (r1 / dist);
s2->x = r2; s2->y = 0; s2->vx = 0; s2->vy = -v_circ * (r2 / dist);
} else {
float dist = 0.3;
float v = sqrtf(39.478 * total_mass / dist) * 0.5;
sys.stars[0].x = dist; sys.stars[0].y = 0; sys.stars[0].vx = 0; sys.stars[0].vy = v;
sys.stars[1].x = -dist*0.5; sys.stars[1].y = dist*0.866; sys.stars[1].vx = -v*0.866; sys.stars[1].vy = -v*0.5;
sys.stars[2].x = -dist*0.5; sys.stars[2].y = -dist*0.866; sys.stars[2].vx = v*0.866; sys.stars[2].vy = -v*0.5;
}
sys.planet_count = rand() % 10;
float current_dist = 0.5 + sys.star_count * 0.2;
float frost_line = 2.7 * sqrtf(total_lum);
for (int i = 0; i < sys.planet_count; i++) {
Planet *p = &sys.planets[i];
current_dist *= rand_float(1.3, 1.8);
float angle = rand_float(0, 6.28);
p->x = cos(angle) * current_dist;
p->y = sin(angle) * current_dist;
float v = sqrtf(39.478 * total_mass / current_dist);
p->vx = -sin(angle) * v;
p->vy = cos(angle) * v;
p->rotation_angle = rand_float(0, 6.28);
bool is_outer = current_dist > frost_line * 0.8;
if (is_outer && rand()%100 < 60) {
p->mass = rand_float(10.0, 300.0);
if (p->mass > 50.0) {
p->type = TYPE_GAS_GIANT;
p->radius = 11.0;
p->color = (rand()%2==0) ? 0xDDCCAAFF : 0xDDAA88FF;
sprintf(p->atmosphere, "H/He");
} else {
p->type = TYPE_ICE_GIANT;
p->radius = 4.0;
p->color = 0xAABBFFFF;
sprintf(p->atmosphere, "H/He/CH4");
}
} else {
p->mass = rand_float(0.1, 5.0);
if (current_dist < 0.1 * total_mass) {
p->type = TYPE_MOLTEN;
p->radius = powf(p->mass, 0.27);
p->color = 0xFF4400FF;
sprintf(p->atmosphere, "Metal Vapor");
} else if (is_outer) {
p->type = TYPE_ICY_WORLD;
p->radius = powf(p->mass, 0.3);
p->color = 0xEEEEFFFF;
sprintf(p->atmosphere, "Thin N2");
} else if (current_dist > 0.8 * sqrtf(total_lum) && current_dist < 1.5 * sqrtf(total_lum) && p->mass > 0.3) {
p->type = TYPE_TERRESTRIAL;
p->radius = powf(p->mass, 0.27);
p->color = 0x008800FF;
sprintf(p->atmosphere, "N2/O2");
} else {
p->type = TYPE_ROCKY;
p->radius = powf(p->mass, 0.27);
p->color = 0x888888FF;
sprintf(p->atmosphere, "Thin CO2");
}
}
// Resources & Density (Scientific Model)
generate_resources(p, sys.stars[0].metallicity);
// Calculate Bulk Density from Composition
// densities: Fe=7.8, Si=3.3, Ni=8.9, H2O=1.0, H/He=~0.2 (highly compressible), Au=19.3, REE=6.0
// Simplified weighted average for density
float w_sum = 0;
float vol_sum = 0; // Volume units
float dens_map[8] = {7.8, 3.3, 8.9, 1.0, 0.1, 0.15, 19.3, 6.0};
for (int r=0; r<8; r++) {
float pct = p->resources[r];
if (pct > 0) {
w_sum += pct;
vol_sum += pct / dens_map[r];
}
}
float uncompressed_density = (vol_sum > 0) ? (w_sum / vol_sum) : 3.0;
// Compression Factor: Larger planets are denser due to gravity compressing the core
// Earth (5.5) vs uncompressed rock (3.3). Factor ~1.6 for 1 Me.
// Simple compression model: D = D_0 * (1 + 0.5 * log10(M_earth))
// Gas Giants: compression is huge. Jupiter density 1.33, Saturn 0.69.
if (p->type == TYPE_GAS_GIANT || p->type == TYPE_ICE_GIANT) {
// Gas physics is complex (degeneracy). Use empirical fit.
if (p->mass > 100) {
// Jupiter-like: Density increases with mass
p->density = 1.33 * powf(p->mass/318.0, 0.3);
} else {
// Saturn-like: Fluffy
p->density = 0.7 * powf(p->mass/95.0, 0.1);
}
} else {
// Solid body compression
float compression = 1.0 + 0.4 * log10f(MAX(0.1, p->mass));
p->density = uncompressed_density * compression;
}
// CALCULATE RADIUS from Mass and Density
// M in Earths, D in g/cm3.
// R_earth = 1.0. M_earth = 1.0. D_earth = 5.51.
// R = (M / (D/5.51))^(1/3)
p->radius = powf(p->mass / (p->density / 5.51), 0.333);
p->gravity = p->mass / powf(p->radius, 2);
p->surface_temp = sys.stars[0].temp * sqrtf(sys.stars[0].radius / (2 * current_dist * 215));
p->is_tidally_locked = (current_dist < 0.4 * total_mass);
if (p->is_tidally_locked) p->rotation_period = sqrtf(powf(current_dist, 3) / total_mass) * 365.25;
else if (p->type == TYPE_GAS_GIANT || p->type == TYPE_ICE_GIANT) p->rotation_period = rand_float(0.3, 0.6);
else p->rotation_period = (rand()%10 < 8) ? rand_float(0.5, 2.0) : rand_float(10.0, 243.0);
sprintf(p->name, "P-%c", 'A' + i);
p->moon_count = (p->type == TYPE_GAS_GIANT) ? (rand() % 4 + 1) : (rand()%100 < 30);
for (int m=0; m<p->moon_count; m++) {
Moon *mn = &p->moons[m];
mn->orbit_dist = p->radius * (1.5 + rand_float(0.5, 2.0));
mn->radius = p->radius * 0.25;
mn->x = rand_float(0, 6.28);
mn->vx = rand_float(1.0, 3.0);
sprintf(mn->name, "M-%d", m+1);
}
}
return sys;
}
--- cland/main.c ---
#include <SDL2/SDL.h>
#include "galaxy.h"
#include "font.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define G 39.478 // Gravity Constant
// Cache Helpers
void init_cache(GameState *state) {
state->visited_count = 0;
state->visited_capacity = 16;
state->visited_systems = (StarSystem*)malloc(sizeof(StarSystem) * state->visited_capacity);
}
int find_cached_system(GameState *state, int x, int y) {
for (int i = 0; i < state->visited_count; i++) {
if (state->visited_systems[i].x == x && state->visited_systems[i].y == y) return i;
}
return -1;
}
void cache_current_system(GameState *state) {
int idx = find_cached_system(state, state->current_system.x, state->current_system.y);
if (idx != -1) state->visited_systems[idx] = state->current_system;
else {
if (state->visited_count >= state->visited_capacity) {
state->visited_capacity *= 2;
state->visited_systems = (StarSystem*)realloc(state->visited_systems, sizeof(StarSystem) * state->visited_capacity);
}
state->visited_systems[state->visited_count++] = state->current_system;
}
}
void draw_circle(SDL_Renderer *renderer, int cx, int cy, int radius) {
for (int w = 0; w < radius * 2; w++) {
for (int h = 0; h < radius * 2; h++) {
int dx = radius - w;
int dy = radius - h;
if ((dx*dx + dy*dy) <= (radius * radius)) {
SDL_RenderDrawPoint(renderer, cx + dx, cy + dy);
}
}
}
}
#include <string.h>
// ...
int main(int argc, char* argv[]) {
// Parse Arguments
for (int i=1; i<argc; i++) {
if (strncmp(argv[i], "--stars=", 8) == 0) {
int count = atoi(argv[i] + 8);
if (count >= 1 && count <= 3) {
set_forced_star_count(count);
printf("Forcing Star Count: %d\n", count);
}
}
}
if (SDL_Init(SDL_INIT_VIDEO) < 0) return 1;
SDL_Window* window = SDL_CreateWindow("C-Galaxy N-Body", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
GameState state;
state.mode = VIEW_GALAXY;
state.camera_pos.x = 0; state.camera_pos.y = 0;
state.zoom = 20.0;
state.running = true;
state.selected_planet_idx = -1;
state.selected_star = false;
state.centered_planet_idx = -1;
state.system_center_offset.x = 0; state.system_center_offset.y = 0;
state.paused = false;
state.time_speed = 1.0f;
state.galaxy_camera_pos.x = 0; state.galaxy_camera_pos.y = 0; state.galaxy_zoom = 20.0;
init_cache(&state);
SDL_Event e;
Uint32 last_time = SDL_GetTicks();
while (state.running) {
Uint32 current_time = SDL_GetTicks();
float dt = (current_time - last_time) / 1000.0f;
last_time = current_time;
while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT) state.running = false;
if (e.type == SDL_MOUSEBUTTONDOWN) {
int mx, my; SDL_GetMouseState(&mx, &my);
if (e.button.button == SDL_BUTTON_LEFT) {
if (state.mode == VIEW_GALAXY) {
float wx = state.camera_pos.x + (mx - SCREEN_WIDTH/2) / state.zoom;
float wy = state.camera_pos.y + (my - SCREEN_HEIGHT/2) / state.zoom;
int ix = round(wx); int iy = round(wy);
if (star_exists(ix, iy)) {
state.galaxy_camera_pos = state.camera_pos; state.galaxy_zoom = state.zoom;
int idx = find_cached_system(&state, ix, iy);
if (idx != -1) state.current_system = state.visited_systems[idx];
else state.current_system = generate_system(ix, iy);
state.mode = VIEW_SYSTEM; state.zoom = 100.0;
state.selected_planet_idx = -1; state.selected_star = true;
state.centered_planet_idx = -1; state.system_center_offset.x = 0; state.system_center_offset.y = 0;
}
} else {
// System Click
int cx = SCREEN_WIDTH/2 - state.system_center_offset.x * state.zoom;
int cy = SCREEN_HEIGHT/2 - state.system_center_offset.y * state.zoom;
state.selected_planet_idx = -1; state.selected_star = false;
// Click Star?
for (int s=0; s<state.current_system.star_count; s++) {
Star *star = &state.current_system.stars[s];
int sx = cx + star->x * state.zoom;
int sy = cy + star->y * state.zoom;
if (sqrt(pow(mx-sx,2)+pow(my-sy,2)) < 30) {
state.selected_star = true;
if (e.button.clicks == 2) { state.centered_planet_idx = -1; state.system_center_offset.x = star->x; state.system_center_offset.y = star->y; }
goto clicked;
}
}
// Click Planet?
for (int i=0; i<state.current_system.planet_count; i++) {
Planet *p = &state.current_system.planets[i];
int px = cx + p->x * state.zoom;
int py = cy + p->y * state.zoom;
if (sqrt(pow(mx-px,2)+pow(my-py,2)) < 20) {
state.selected_planet_idx = i;
if (e.button.clicks == 2) state.centered_planet_idx = i;
goto clicked;
}
}
clicked:;
}
}
if (e.button.button == SDL_BUTTON_RIGHT && state.mode == VIEW_SYSTEM) {
cache_current_system(&state);
state.mode = VIEW_GALAXY;
state.camera_pos = state.galaxy_camera_pos; state.zoom = state.galaxy_zoom;
}
}
if (e.type == SDL_KEYDOWN) {
if (e.key.keysym.sym == SDLK_LEFT) state.camera_pos.x -= 10.0/state.zoom*20;
if (e.key.keysym.sym == SDLK_RIGHT) state.camera_pos.x += 10.0/state.zoom*20;
if (e.key.keysym.sym == SDLK_UP) {
if (state.mode == VIEW_GALAXY) state.camera_pos.y -= 10.0/state.zoom*20;
else state.time_speed *= 1.5;
}
if (e.key.keysym.sym == SDLK_DOWN) {
if (state.mode == VIEW_GALAXY) state.camera_pos.y += 10.0/state.zoom*20;
else state.time_speed /= 1.5;
}
if (e.key.keysym.sym == SDLK_SPACE) state.paused = !state.paused;
if (e.key.keysym.sym == SDLK_EQUALS) state.zoom *= 1.1;
if (e.key.keysym.sym == SDLK_MINUS) state.zoom /= 1.1;
}
if (e.type == SDL_MOUSEWHEEL) state.zoom *= (e.wheel.y > 0 ? 1.1 : 0.9);
}
// --- N-BODY PHYSICS ---
if (state.mode == VIEW_SYSTEM && !state.paused) {
float sim_dt = dt * state.time_speed * 0.5;
// Update Stars
if (state.current_system.star_count > 1) {
for (int i=0; i<state.current_system.star_count; i++) {
Star *s1 = &state.current_system.stars[i];
float ax = 0, ay = 0;
for (int j=0; j<state.current_system.star_count; j++) {
if (i==j) continue;
Star *s2 = &state.current_system.stars[j];
float dx = s2->x - s1->x;
float dy = s2->y - s1->y;
float r2 = dx*dx + dy*dy;
float r = sqrt(r2);
float f = G * s2->mass / r2;
ax += f * dx / r;
ay += f * dy / r;
}
s1->vx += ax * sim_dt; s1->vy += ay * sim_dt;
}
for (int i=0; i<state.current_system.star_count; i++) {
state.current_system.stars[i].x += state.current_system.stars[i].vx * sim_dt;
state.current_system.stars[i].y += state.current_system.stars[i].vy * sim_dt;
}
}
// Update Planets
for (int i=0; i<state.current_system.planet_count; i++) {
Planet *p = &state.current_system.planets[i];
float ax = 0, ay = 0;
for (int s=0; s<state.current_system.star_count; s++) {
Star *star = &state.current_system.stars[s];
float dx = star->x - p->x;
float dy = star->y - p->y;
float r2 = dx*dx + dy*dy;
float r = sqrt(r2);
float f = G * star->mass / r2;
ax += f * dx / r;
ay += f * dy / r;
}
p->vx += ax * sim_dt; p->vy += ay * sim_dt;
p->x += p->vx * sim_dt; p->y += p->vy * sim_dt;
// Update Rotation
float rot_speed = 6.28 / (p->rotation_period / 365.25);
p->rotation_angle += rot_speed * sim_dt;
for (int m=0; m<p->moon_count; m++) {
p->moons[m].x += p->moons[m].vx * sim_dt * 5.0;
}
}
if (state.centered_planet_idx != -1) {
Planet *p = &state.current_system.planets[state.centered_planet_idx];
state.system_center_offset.x = p->x;
state.system_center_offset.y = p->y;
}
}
// Render
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
if (state.mode == VIEW_GALAXY) {
int view_w = SCREEN_WIDTH / state.zoom;
int view_h = SCREEN_HEIGHT / state.zoom;
for (int x = (int)state.camera_pos.x - view_w/2; x <= (int)state.camera_pos.x + view_w/2; x++) {
for (int y = (int)state.camera_pos.y - view_h/2; y <= (int)state.camera_pos.y + view_h/2; y++) {
if (star_exists(x, y)) {
int sx = (x - state.camera_pos.x) * state.zoom + SCREEN_WIDTH/2;
int sy = (y - state.camera_pos.y) * state.zoom + SCREEN_HEIGHT/2;
unsigned int s = get_seed(x,y); srand(s);
float temp = 5778 * powf(rand_float(0.1, 5.0), 0.5);
int r=255,g=255,b=255;
if (temp > 10000) {r=136;g=136;} else if (temp<4000) {g=68;b=0;} else if (temp<6000) {b=0;}
SDL_SetRenderDrawColor(renderer, r, g, b, 255);
draw_circle(renderer, sx, sy, MAX(2, state.zoom/5));
}
}
}
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
draw_text(renderer, 10, 10, "GALAXY N-BODY - Click star", 2);
} else {
// Draw System
int cx = SCREEN_WIDTH/2 - state.system_center_offset.x * state.zoom;
int cy = SCREEN_HEIGHT/2 - state.system_center_offset.y * state.zoom;
// Stars
for (int i=0; i<state.current_system.star_count; i++) {
Star *s = &state.current_system.stars[i];
int sx = cx + s->x * state.zoom;
int sy = cy + s->y * state.zoom;
Uint32 c = s->color;
SDL_SetRenderDrawColor(renderer, (c>>24)&0xFF, (c>>16)&0xFF, (c>>8)&0xFF, 255);
draw_circle(renderer, sx, sy, MAX(10, s->radius * 5 * (state.zoom/100.0)));
}
// Planets
for (int i=0; i<state.current_system.planet_count; i++) {
Planet *p = &state.current_system.planets[i];
int px = cx + p->x * state.zoom;
int py = cy + p->y * state.zoom;
// Draw Body
Uint32 c = p->color;
SDL_SetRenderDrawColor(renderer, (c>>24)&0xFF, (c>>16)&0xFF, (c>>8)&0xFF, 255);
int radius = (p->type==TYPE_GAS_GIANT)?6:3;
draw_circle(renderer, px, py, radius);
// Draw Rotation Line
int rx = px + cos(p->rotation_angle) * radius;
int ry = py + sin(p->rotation_angle) * radius;
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawLine(renderer, px, py, rx, ry);
// Moons
for (int m=0; m<p->moon_count; m++) {
Moon *mn = &p->moons[m];
int mx = px + cos(mn->x) * mn->orbit_dist * (state.zoom/50.0 + 2.0);
int my = py + sin(mn->x) * mn->orbit_dist * (state.zoom/50.0 + 2.0);
SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255);
draw_circle(renderer, mx, my, 1);
}
SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255);
draw_text(renderer, px, py+10, p->name, 1);
}
// UI
char buf[256];
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
sprintf(buf, "SYSTEM: %s (%d Stars) | Speed: %.1f", state.current_system.stars[0].name, state.current_system.star_count, state.time_speed);
draw_text(renderer, 10, 10, buf, 2);
int y=50;
if (state.selected_star) {
Star *s = &state.current_system.stars[0];
draw_text(renderer, 10, y+=15, "SELECTED STAR:", 1);
sprintf(buf, "Name: %s", s->name); draw_text(renderer, 10, y+=15, buf, 1);
sprintf(buf, "Mass: %.2f Sol", s->mass); draw_text(renderer, 10, y+=15, buf, 1);
sprintf(buf, "Lum: %.2f Lsol", s->luminosity); draw_text(renderer, 10, y+=15, buf, 1);
sprintf(buf, "Temp: %.0f K", s->temp); draw_text(renderer, 10, y+=15, buf, 1);
} else if (state.selected_planet_idx != -1) {
Planet *p = &state.current_system.planets[state.selected_planet_idx];
draw_text(renderer, 10, y+=15, "SELECTED PLANET:", 1);
sprintf(buf, "Name: %s", p->name); draw_text(renderer, 10, y+=15, buf, 1);
sprintf(buf, "Mass: %.2f", p->mass); draw_text(renderer, 10, y+=15, buf, 1);
sprintf(buf, "Temp: %.0f K", p->surface_temp); draw_text(renderer, 10, y+=15, buf, 1);
sprintf(buf, "Atm: %s", p->atmosphere); draw_text(renderer, 10, y+=15, buf, 1);
sprintf(buf, "Locked: %s", p->is_tidally_locked?"YES":"NO"); draw_text(renderer, 10, y+=15, buf, 1);
sprintf(buf, "Day: %.2f Days", p->rotation_period); draw_text(renderer, 10, y+=15, buf, 1);
sprintf(buf, "Moons: %d", p->moon_count); draw_text(renderer, 10, y+=15, buf, 1);
draw_text(renderer, 10, y+=15, "RESOURCES:", 1);
// Find Top 3
for (int k=0; k<3; k++) {
int max_idx = -1;
float max_val = -1.0;
for (int r=0; r<8; r++) {
if (p->resources[r] > max_val) {
bool used = false;
// Check if already displayed (hacky sort)
// To do this properly without modifying struct, we need a temp array or just print > threshold
// Let's just print any > 10% for now or fix sort
}
}
}
// Simpler approach: Iterate and print all > 15%
const char* res_names[] = {"Iron", "Silicon", "Nickel", "Water", "Hydrogen", "Helium", "Gold", "RareEarths"};
int printed = 0;
for (int r=0; r<8; r++) {
if (p->resources[r] > 15.0) {
sprintf(buf, " %s: %.1f%%", res_names[r], p->resources[r]);
draw_text(renderer, 10, y+=15, buf, 1);
printed++;
}
}
if (printed == 0) draw_text(renderer, 10, y+=15, " Trace Elements", 1);
}
}
SDL_RenderPresent(renderer);
}
free(state.visited_systems);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment