cepheid

An Aurora 4X clone
Log | Files | Refs | README

commit b47747cd7e322292fc65e24858b2bac6839652fa
parent 6282e42d22db93c841e2fd8137c20ff2bf1c2b6e
Author: hhvn <dev@hhvn.uk>
Date:   Fri, 19 Aug 2022 13:57:43 +0100

Smooth & performant orbits

Diffstat:
AREADME | 18++++++++++++++++++
Mdata/Makefile | 4+++-
Msrc/data.c | 6+++---
Asrc/db.c | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.c | 44+++++++++++++++++++++++++++++++++-----------
Msrc/main.h | 48++++++++++++++++++++++++++++++++++++++----------
Msrc/maths.h | 2++
Msrc/save.c | 44+++++++++++++++++++++++++++++---------------
Msrc/str.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/struct.h | 29++++++++++++++++++++++++++++-
Msrc/style.h | 36+++++++++++++++---------------------
Msrc/system.c | 57++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/ui.c | 470+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Asrc/views.h | 41+++++++++++++++++++++++++++++++++++++++++
14 files changed, 761 insertions(+), 223 deletions(-)

diff --git a/README b/README @@ -0,0 +1,18 @@ +Concessions +=========== + +Aurora and this game have both made many concessions when it comes to physics +and astronomy in order to make something playable. Some of them are listed here +(excluding fictional tech like jump points). + +Physics: + - The movement of ships solely relies on their max speed: there is no + acceleration, nor gravitational effect. + +Astronomy: + - Retrograde orbits are not modelled. (Where they exist in the solar system: + eg, Triton and Phoebe, they are modelled as prograde orbits). + - All system bodies are assumed to be spherical. + - All oribts (excluding comets) are assumed to be spherical. + - Comets "orbit" in a straight line extending outward from the centre of a + star. diff --git a/data/Makefile b/data/Makefile @@ -7,7 +7,9 @@ all: $(HEADERS) sol sol: worlds.tsv worlds-parse.awk rm -rf sol ./worlds-parse.awk < $< - printf "main\tsol\n" > sol/index + printf "main\tSol\n" > sol/index + printf "x\t0\n" >> sol/index + printf "y\t0\n" >> sol/index # Thanks Jonathan worlds.tsv: diff --git a/src/data.c b/src/data.c @@ -44,7 +44,8 @@ unsigned char settings_png[] = { #define IMAGE_LOAD(name) \ raw_image_##name = LoadImageFromMemory(".png", \ name##_png, sizeof(name##_png)); \ - image_##name = LoadTextureFromImage(raw_image_##name) + image_##name = LoadTextureFromImage(raw_image_##name); \ + UnloadImage(raw_image_##name) void data_load(void) { @@ -59,8 +60,7 @@ data_load(void) { } #define IMAGE_UNLOAD(name) \ - UnloadTexture(image_##name); \ - UnloadImage(raw_image_##name); + UnloadTexture(image_##name) void data_unload(void) { diff --git a/src/db.c b/src/db.c @@ -0,0 +1,112 @@ +#include <stdio.h> +#include <stdlib.h> +#include "main.h" + +int +vdbsetf(char *dir, char *group, char *key, char *fmt, va_list args) { + va_list ap; + char *str; + int ret; + + va_copy(ap, args); + str = vsmprintf(fmt, ap); + va_end(ap); + ret = dbset(dir, group, key, str); + free(str); + return ret; +} + +int +dbsetf(char *dir, char *group, char *key, char *fmt, ...) { + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vdbsetf(dir, group, key, fmt, ap); + va_end(ap); + return ret; +} + +int +dbsetint(char *dir, char *group, char *key, int val) { + return dbsetf(dir, group, key, "%d", val); +} + +int +dbsetfloat(char *dir, char *group, char *key, float val) { + return dbsetf(dir, group, key, "%f", val); +} + +void +dbsetbody(System *sys, Body *body) { + char *group; + char *parent; + enum BodyType type; + + group = smprintf("%s/%s", sys->name, body->name); + + if (body->parent) + parent = body->parent->name; + else + parent = sys->name; + dbset(save->db.systems, group, "parent", parent); + + dbset(save->db.systems, group, "type", bodytype_strify(body)); + dbsetfloat(save->db.systems, group, "radius", body->radius); + dbsetfloat(save->db.systems, group, "mass", body->mass); + dbsetfloat(save->db.systems, group, "orbdays", body->orbdays); + if (body->type == BODY_COMET) { + dbsetfloat(save->db.systems, group, "mindist", 0 - body->mindist); /* see sys_load() */ + dbsetfloat(save->db.systems, group, "maxdist", body->maxdist); + dbsetfloat(save->db.systems, group, "curdist", body->curdist); + dbsetfloat(save->db.systems, group, "theta", body->theta); + dbsetfloat(save->db.systems, group, "inward", body->inward); + } else { + dbsetfloat(save->db.systems, group, "dist", body->dist); + dbsetfloat(save->db.systems, group, "curtheta", body->curtheta); + } + free(group); +} + +int +vdbgetf(char *dir, char *group, char *key, char *fmt, va_list args) { + va_list ap; + char *str; + int ret; + + str = dbget(dir, group, key); + if (!str) + return EOF; + va_copy(ap, args); + ret = vsscanf(str, fmt, ap); + va_end(ap); + free(str); + return ret; +} + +int +dbgetf(char *dir, char *group, char *key, char *fmt, ...) { + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vdbgetf(dir, group, key, fmt, ap); + va_end(ap); + return ret; +} + +int +dbgetint(char *dir, char *group, char *key) { + int ret; + + dbgetf(dir, group, key, "%d", &ret); + return ret; +} + +float +dbgetfloat(char *dir, char *group, char *key) { + float ret; + + dbgetf(dir, group, key, "%f", &ret); + return ret; +} diff --git a/src/main.c b/src/main.c @@ -1,4 +1,5 @@ #include <stdio.h> +#include <time.h> #include <raylib.h> #include "main.h" @@ -8,18 +9,23 @@ Save *save = NULL; int main(void) { - char *system; + int draw; + int view_prev = -1; + time_t lastevent; ui_init(); data_load(); + save_read(TESTSAVE); - save = save_init(TESTSAVE); - system = dbget(save->db.dir, "index", "selsystem"); - if (!system) system = "Sol"; /* TODO: 1. selsystem, 2. player home, 3. uhh? */ - save->system = sys_load(NULL, system); + lastevent = time(NULL); + + ui_update_screen(); while (!WindowShouldClose()) { - ui_clickable_update(); + ffree(); + + if (IsWindowResized()) + ui_update_screen(); if (IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT)) { /* AAAAAAAAAAHHHHHHHHHHHH. WHY NOT JUST USE KEY_1, KEY_2..! */ @@ -31,18 +37,34 @@ main(void) { else if (IsKeyPressed(KEY_SIX)) view_tabs.sel = 5; } - view_handlers[view_tabs.sel](); + draw = view_handlers[view_tabs.sel](view_prev != view_tabs.sel); + + if (lastevent == time(NULL)) + draw = 1; + + view_prev = view_tabs.sel; + + if (view_prev != view_tabs.sel || + GetMouseWheelMove() || + IsWindowResized() || + ui_clickable_update() || + IsMouseButtonDown(MOUSE_BUTTON_LEFT) || + IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) + lastevent = time(NULL); BeginDrawing(); - ClearBackground(COL_BG); - view_drawers[view_tabs.sel](); + if (draw) { + ClearBackground(COL_BG); + ui_clickable_clear(); + view_drawers[view_tabs.sel](); + } ui_draw_views(); EndDrawing(); } + data_unload(); ui_deinit(); - - save_write(save); + save_write(); dbcleanup(); return 0; } diff --git a/src/main.h b/src/main.h @@ -12,6 +12,10 @@ extern Save *save; /* str.c */ +void * falloc(size_t size); +void ffree(void); +char * vsfprintf(char *fmt, va_list args); +char * sfprintf(char *fmt, ...); /* return string allocated for current frame */ char * vsmprintf(char *fmt, va_list args); char * smprintf(char *fmt, ...); /* return allocated string */ char * nstrdup(char *str); /* NULL-safe */ @@ -25,18 +29,27 @@ float strnum(char *str); /* ui.c */ extern Tabs view_tabs; -extern void (*view_handlers[UI_VIEW_LAST])(void); +extern int (*view_handlers[UI_VIEW_LAST])(int); extern void (*view_drawers[UI_VIEW_LAST])(void); +extern Rect screen_rect; +extern View_main view_main; +extern View_sys view_sys; void ui_init(void); +void ui_update_screen(void); void ui_deinit(void); void ui_print(int x, int y, Color col, char *format, ...); int ui_textsize(char *text); int ui_collides(Rect rect, Vector2 point); +int ui_onscreen(Vector2 point); +int ui_onscreen_ring(Vector2 centre, float r); +int ui_onscreen_circle(Vector2 centre, float r); void ui_clickable_register(int x, int y, int w, int h, enum UiElements type, void *elem); void ui_clickable_handle(Vector2 mouse, MouseButton button, Clickable *clickable); -void ui_clickable_update(void); +int ui_clickable_update(void); +void ui_clickable_clear(void); void ui_draw_views(void); void ui_draw_border(int x, int y, int w, int h, int px); +void ui_draw_ring(int x, int y, float r, Color col); void ui_draw_tabs(int x, int y, int w, int h, Tabs *tabs); void ui_draw_tabbed_window(int x, int y, int w, int h, Tabs *tabs); void ui_draw_checkbox(int x, int y, Checkbox *checkbox); @@ -46,12 +59,12 @@ Vector2 ui_vectordiff(Vector2 a, Vector2 b); float ui_vectordist(Vector2 a, Vector2 b); int ui_should_draw_body(Body *body, int orbit); void ui_draw_body(Body *body); -void ui_handle_view_main(void); -void ui_handle_view_colonies(void); -void ui_handle_view_fleets(void); -void ui_handle_view_design(void); -void ui_handle_view_sys(void); -void ui_handle_view_settings(void); +int ui_handle_view_main(int nowsel); +int ui_handle_view_colonies(int nowsel); +int ui_handle_view_fleets(int nowsel); +int ui_handle_view_design(int nowsel); +int ui_handle_view_sys(int nowsel); +int ui_handle_view_settings(int nowsel); void ui_draw_view_main(void); void ui_draw_view_colonies(void); void ui_draw_view_fleets(void); @@ -65,15 +78,19 @@ char * bodytype_strify(Body *body); Vector2 sys_vectorize(Polar polar); Vector2 sys_vectorize_around(Vector2 around, Polar polar); Polar sys_polarize(Vector2 vector); +Polar sys_polarize_around(Vector2 around, Vector2 vector); Polar sys_sum_polar(Polar absolute, Polar relative); Vector2 sys_get_vector(Body *body); Polar sys_get_polar(Body *body); +float sys_add_theta(float theta, float add); System *sys_init(char *name); System *sys_load(System *s, char *name); +System *sys_get(char *name); +System *sys_default(void); /* save.c */ -Save * save_init(char *savedir); -void save_write(Save *s); +void save_read(char *dir); +void save_write(void); /* data.c */ extern Font font; @@ -85,3 +102,14 @@ extern Texture image_sys; extern Texture image_settings; void data_load(void); void data_unload(void); + +/* db.c */ +int vdbsetf(char *dir, char *group, char *key, char *fmt, va_list args); +int dbsetf(char *dir, char *group, char *key, char *fmt, ...); +int dbsetint(char *dir, char *group, char *key, int val); +int dbsetfloat(char *dir, char *group, char *key, float val); +void dbsetbody(System *sys, Body *body); +int vdbgetf(char *dir, char *group, char *key, char *fmt, va_list args); +int dbgetf(char *dir, char *group, char *key, char *fmt, ...); +int dbgetint(char *dir, char *group, char *key); +float dbgetfloat(char *dir, char *group, char *key); diff --git a/src/maths.h b/src/maths.h @@ -16,3 +16,5 @@ #define WEEK_LEN (7 * DAY_LEN) #define YEAR_LEN (365.25 * DAY_LEN) #define MONTH_APPROX (YEAR_LEN / 12) + +#define SQUARE(a) (a * a) diff --git a/src/save.c b/src/save.c @@ -2,25 +2,39 @@ #include <string.h> #include "main.h" -Save * -save_init(char *dir) { - Save *ret; +static void +save_free(void) { + free(save->db.dir); + free(save->db.races); + free(save->db.systems); + free(save->db.fleets); + /* free systems? */ + free(save); +} + +void +save_read(char *dir) { + char *str; + + if (save) + save_free(); - if (!dir || !(ret = malloc(sizeof(Save)))) - return NULL; + if (!dir || !(save = malloc(sizeof(Save)))) + return; dbdeclare(dir); - ret->db.dir = nstrdup(dir); - ret->db.races = smprintf("%s/Races", dir); - ret->db.systems = smprintf("%s/Systems", dir); - ret->db.fleets = smprintf("%s/Fleets", dir); - ret->system = NULL; - return ret; + save->db.dir = nstrdup(dir); + save->db.races = smprintf("%s/Races", dir); + save->db.systems = smprintf("%s/Systems", dir); + save->db.fleets = smprintf("%s/Fleets", dir); + if ((str = dbget(save->db.dir, "index", "homesystem"))) + save->homesys = sys_get(str); + return; }; void -save_write(Save *s) { - if (s->system && s->system->name) - dbset(save->db.dir, "index", "selsystem", s->system->name); - dbwrite(s->db.dir); +save_write(void) { + if (view_main.sys) + dbset(save->db.dir, "index", "selsystem", view_main.sys->name); + dbwrite(save->db.dir); } diff --git a/src/str.c b/src/str.c @@ -7,6 +7,50 @@ #include <math.h> #include "main.h" +#define FMEMMAX 1024 + +static void *fmem[FMEMMAX] = {NULL}; +static int fmemi = 0; + +/* allocate for a frame */ +void * +falloc(size_t size) { + if (fmemi + 1 == FMEMMAX) { + errno = ENOMEM; + return NULL; + } + return (fmem[fmemi++] = malloc(size)); +} + +void +ffree(void) { + while (fmemi--) { + free(fmem[fmemi]); + fmem[fmemi] = NULL; + } + fmemi = 0; +} + +char * +vsfprintf(char *fmt, va_list ap) { + if (fmemi + 1 == FMEMMAX) { + errno = ENOMEM; + return NULL; + } + return (fmem[fmemi++] = vsmprintf(fmt, ap)); +} + +char * +sfprintf(char *fmt, ...) { + va_list ap; + char *ret; + + va_start(ap, fmt); + ret = vsfprintf(fmt, ap); + va_end(ap); + return ret; +} + char * vsmprintf(char *fmt, va_list args) { va_list ap; @@ -51,41 +95,30 @@ nstrdup(char *str) { char * strkmdist(float km) { - static char *ret = NULL; - - free(ret); - if (km > GIGA) - ret = smprintf("%.2fb km", km / GIGA); + return sfprintf("%.2fb km", km / GIGA); else if (km > MEGA) - ret = smprintf("%.2fm km", km / MEGA); + return sfprintf("%.2fm km", km / MEGA); else if (km > KILO) - ret = smprintf("%.2fk km", km / KILO); + return sfprintf("%.2fk km", km / KILO); else - ret = smprintf("%.2f km", km); - - return ret; + return sfprintf("%.2f km", km); } char * strlightdist(float km) { - static char *ret = NULL; float ls = km * KILO / C_MS; - free(ret); - if (ls > YEAR_LEN) - ret = smprintf("%.2f ly", ls / YEAR_LEN); + return sfprintf("%.2f ly", ls / YEAR_LEN); else if (ls > DAY_LEN) - ret = smprintf("%.2f ld", ls / DAY_LEN); + return sfprintf("%.2f ld", ls / DAY_LEN); else if (ls > HOUR_LEN) - ret = smprintf("%.2f lh", ls / HOUR_LEN); + return sfprintf("%.2f lh", ls / HOUR_LEN); else if (ls > MIN_LEN) - ret = smprintf("%.2f lm", ls / MIN_LEN); + return sfprintf("%.2f lm", ls / MIN_LEN); else - ret = smprintf("%.2f ls", ls); - - return ret; + return sfprintf("%.2f ls", ls); } int diff --git a/src/struct.h b/src/struct.h @@ -56,6 +56,15 @@ typedef struct { char *name; Body **bodies; size_t bodies_len; + Body *furthest_body; + struct { + int stars; + int planets; + int asteroids; + int comets; + int moons; + } num; + Vector2 lypos; } System; /* save.c */ @@ -66,7 +75,7 @@ typedef struct { char *systems; char *fleets; } db; - System *system; + System *homesys; } Save; /* ui.c */ @@ -76,6 +85,13 @@ typedef struct { int w, h; } Rect; +typedef struct { + float w, h; + float diag; + Vector2 centre; + Rect rect; /* for passing to functions */ +} Screen; + #define TABS_MAX 16 typedef struct { int n; @@ -88,6 +104,7 @@ typedef struct { } Tabs; typedef struct { + int enabled; int val; char *label; } Checkbox; @@ -113,3 +130,13 @@ typedef struct { enum UiElements type; void *elem; } Clickable; + +#include "views.h" /* unique structures */ + +/* maths.c */ +typedef struct { + float median; + float uquart; + float lquart; + size_t elems; +} Nstats; diff --git a/src/style.h b/src/style.h @@ -26,27 +26,15 @@ #undef MAGENTA #undef RAYWHITE -#define X_PUREWHITE 0xffffffff -#define X_WHITE 0xe6e6e6ff -#define X_AURORA_GREY 0x1e1e1eff -#define X_AURORA_BLUE 0x00003cff -#define X_DARKBLUE 0x00002cff -#define X_AURORA_GREEN 0x649696ff -#define X_TRANSPARENT 0x00000000 - -#define UNHEX(col) ((Color){ \ - ((col & (0xff << 24)) >> 24), \ - ((col & (0xff << 16)) >> 16), \ - ((col & (0xff << 8)) >> 8), \ - (col & 0xff)}) - -#define PUREWHITE UNHEX(X_PUREWHITE) -#define WHITE UNHEX(X_WHITE) -#define AURORA_GREY UNHEX(X_AURORA_GREY) -#define AURORA_BLUE UNHEX(X_AURORA_BLUE) -#define DARKBLUE UNHEX(X_DARKBLUE) -#define AURORA_GREEN UNHEX(X_AURORA_GREEN) -#define TRANSPARENT UNHEX(X_TRANSPARENT) +#define PUREWHITE ((Color){ 0xff, 0xff, 0xff, 0xff }) +#define WHITE ((Color){ 0xe6, 0xe6, 0xe6, 0xff }) +#define AURORA_GREY ((Color){ 0x1e, 0x1e, 0x1e, 0xff }) +#define AURORA_BLUE ((Color){ 0x00, 0x00, 0x3c, 0xff }) +#define DARKBLUE ((Color){ 0x00, 0x00, 0x2c, 0xff }) +#define AURORA_GREEN ((Color){ 0x64, 0x96, 0x96, 0xff }) +#define TRANSPARENT ((Color){ 0x00, 0x00, 0x00, 0x00 }) +#define DUSTORANGE ((Color){ 0xbb, 0x88, 0x44, 0xff }) +#define ICEBLUE ((Color){ 0x77, 0x77, 0xff, 0xff }) /* colours */ #define COL_FG WHITE @@ -55,6 +43,12 @@ #define COL_BORDER AURORA_GREY #define COL_INFO AURORA_GREEN #define COL_ORBIT AURORA_GREEN +#define COL_STAR DUSTORANGE +#define COL_COMET ICEBLUE +#define COL_PLANET WHITE +#define COL_MOON WHITE +#define COL_DWARF WHITE +#define COL_ASTEROID AURORA_GREEN /* font */ #define FONT_SIZE 10 diff --git a/src/system.c b/src/system.c @@ -4,7 +4,7 @@ #include "raylib.h" #include "main.h" -char *bodytype_names[BODY_LAST] = { +static char *bodytype_names[BODY_LAST] = { [BODY_STAR] = "Star", [BODY_PLANET] = "Planet", [BODY_DWARF] = "Dwarf planet", @@ -67,6 +67,12 @@ sys_polarize(Vector2 vector) { } Polar +sys_polarize_around(Vector2 around, Vector2 vector) { + Vector2 v = {vector.y - around.y, vector.x - around.x}; + return sys_polarize(v); +} + +Polar sys_sum_polar(Polar absolute, Polar relative) { return sys_polarize(sys_vectorize_around(sys_vectorize(absolute), relative)); } @@ -106,6 +112,17 @@ sys_get_polar(Body *body) { return polar; } +float +sys_add_theta(float theta, float add) { + float ret; + ret = theta + add; + while (ret > 360) + ret -= 360; + while (ret < 0) + ret += 360; + return ret; +} + System * sys_init(char *name) { System *ret = malloc(sizeof(System)); @@ -113,6 +130,8 @@ sys_init(char *name) { ret->name = nstrdup(name); ret->bodies = NULL; ret->bodies_len = 0; + ret->furthest_body = NULL; + memset(&ret->num, 0, sizeof(ret->num)); return ret; } @@ -138,6 +157,9 @@ sys_load(System *s, char *name) { if (!s) s = sys_init(name); + s->lypos.x = strnum(dbget(save->db.systems, name, "x")); + s->lypos.y = strnum(dbget(save->db.systems, name, "y")); + dir = smprintf("%s/", s->name); s->bodies_len = blen = dblistgroups_f(&bname, save->db.systems, &filter_bodyinsystem, dir); if (!bname) return NULL; @@ -156,6 +178,14 @@ sys_load(System *s, char *name) { tmp = dbget(save->db.systems, bname[i], "type"); s->bodies[i]->type = bodytype_enumify(tmp); + switch (s->bodies[i]->type) { + case BODY_STAR: s->num.stars++; break; + case BODY_PLANET: s->num.planets++; break; + case BODY_ASTEROID: s->num.asteroids++; break; + case BODY_COMET: s->num.comets++; break; + case BODY_MOON: s->num.moons++; break; + } + s->bodies[i]->radius = strnum(dbget(save->db.systems, bname[i], "radius")); s->bodies[i]->mass = strnum(dbget(save->db.systems, bname[i], "mass")); s->bodies[i]->orbdays = strnum(dbget(save->db.systems, bname[i], "orbdays")); @@ -190,6 +220,14 @@ sys_load(System *s, char *name) { sys_get_polar(s->bodies[i]); /* Builds the cache for us: this is more efficient as it can cache the parent too */ s->bodies[i]->vector = sys_vectorize(s->bodies[i]->polar); + + /* This could deal with moons, but that's probably not useful. + * What about multiple stars in a system? That may need to be + * addressed in future */ + if (s->bodies[i]->parent && s->bodies[i]->parent->type == BODY_STAR && (!s->furthest_body || + (s->bodies[i]->type == BODY_COMET ? s->bodies[i]->maxdist : s->bodies[i]->dist) > + (s->furthest_body->type == BODY_COMET ? s->furthest_body->maxdist : s->furthest_body->dist))) + s->furthest_body = s->bodies[i]; } for (i = 0; i < blen; i++) { @@ -201,3 +239,20 @@ sys_load(System *s, char *name) { return s; } + +System * +sys_get(char *name) { + /* For now, call sys_load. In future, get the system via save. */ + return sys_load(NULL, name); +} + +System * +sys_default(void) { + char *str; + if (view_main.sys) + return view_main.sys; + else if ((str = dbget(save->db.dir, "index", "selsystem"))) + return sys_get(str); + else + return save->homesys; +} diff --git a/src/ui.c b/src/ui.c @@ -1,4 +1,5 @@ #include <math.h> +#include <ctype.h> #include <stdlib.h> #include <string.h> #include <raylib.h> @@ -8,12 +9,31 @@ #define VIEWS_HEIGHT 25 #define WINDOW_TAB_HEIGHT 20 #define TARGET_FPS 60 -#define MIN_BODY_DIAM 3 -#define SCREEN_RECT() ((Rect){0, 0, GetScreenWidth(), GetScreenHeight()}) +#define EXPLODE_RECT(rect) rect.x, rect.y, rect.w, rect.h +#define RLIFY_RECT(rect) ((Rectangle){ EXPLODE_RECT(rect) }) static Clickable clickable[CLICKABLE_MAX]; -void (*view_handlers[UI_VIEW_LAST])(void) = { +static float min_body_rad[] = { + [BODY_STAR] = 4, + [BODY_PLANET] = 3, + [BODY_COMET] = 2.5, + [BODY_DWARF] = 2, + [BODY_ASTEROID] = 1, + [BODY_MOON] = 1, +}; + +static Color body_col[] = { + [BODY_STAR] = COL_STAR, + [BODY_PLANET] = COL_PLANET, + [BODY_COMET] = COL_COMET, + [BODY_DWARF] = COL_DWARF, + [BODY_ASTEROID] = COL_ASTEROID, + [BODY_MOON] = COL_MOON, +}; + +/* Return 1 for redraw, 0 to keep prev */ +int (*view_handlers[UI_VIEW_LAST])(int) = { [UI_VIEW_MAIN] = ui_handle_view_main, [UI_VIEW_COLONIES] = ui_handle_view_colonies, [UI_VIEW_FLEETS] = ui_handle_view_fleets, @@ -31,6 +51,8 @@ void (*view_drawers[UI_VIEW_LAST])(void) = { [UI_VIEW_SETTINGS] = ui_draw_view_settings, }; +Screen screen = { 0 }; + Tabs view_tabs = { /* Tactical is the terminology used in Aurora, so I decided to use it * in the ui; in the code it's just called 'main' for my fingers' sake */ @@ -42,48 +64,24 @@ Tabs view_tabs = { {&image_settings, "Settings", 0}} }; -static struct { - struct { - Tabs tabs; - struct { - Checkbox dwarf; - Checkbox asteroid; - Checkbox comet; - } names; - struct { - Checkbox dwarf; - Checkbox asteroid; - Checkbox comet; - } orbit; - Rect geom; - } infobox; - int pan; - struct { - int held; - Vector2 origin; - } ruler; - float kmx, kmy; - float kmperpx; - struct { - int x, y; /* real y = GetScreenHeight() - y */ - int w, h; - } scale; - System *system; -} view_main = { +View_main view_main = { .infobox = { .tabs = { 2, 0, {{NULL, "Display", 0}, {NULL, "Minerals", 0}} }, .names = { - .dwarf = {1, "Name: dwarf planets"}, - .asteroid = {0, "Name: asteroid"}, - .comet = {1, "Name: comet"}, + .dwarf = {1, 1, "Name: dwarf planets"}, + .dwarfn = {1, 0, "Name: numbered dwarf planets"}, /* TODO */ + .asteroid = {1, 0, "Name: asteroids"}, + .asteroidn = {1, 0, "Name: numbered asteroids"}, /* TODO */ + .comet = {1, 1, "Name: comets"}, }, .orbit = { - .dwarf = {0, "Orbit: dwarf planets"}, - .asteroid = {0, "Orbit: asteroid"}, - .comet = {0, "Orbit: comet"}, + .dwarf = {1, 0, "Orbit: dwarf planets"}, + .asteroid = {1, 0, "Orbit: asteroids"}, + .comet = {1, 0, "Orbit: comets"}, }, + .comettail = {1, 1, "Comet tails"}, /* TODO */ .geom = { .x = 10, .y = VIEWS_HEIGHT + 10, @@ -102,14 +100,42 @@ static struct { .w = 50, .h = 3, }, - .system = NULL, + .sys = NULL, +}; + +View_sys view_sys = { + .info = { + .geom = { + .x = 0, + .y = VIEWS_HEIGHT, + .w = 300, + .h = 0, /* see ui_handle_view_sys() */ + }, + }, + .pan = 0, + .off = { + .x = 0, + .y = 0, + }, + .lytopx = 0.025, + .sel = NULL, }; void ui_init(void) { - InitWindow(500, 500, "testing raylib"); + InitWindow(500, 500, ""); SetWindowState(FLAG_WINDOW_RESIZABLE); SetTargetFPS(TARGET_FPS); + SetExitKey(KEY_NULL); +} + +void +ui_update_screen(void) { + screen.w = screen.rect.w = GetScreenWidth(); + screen.h = screen.rect.h = GetScreenHeight(); + screen.centre.x = screen.w / 2; + screen.centre.y = screen.h / 2; + screen.diag = sqrt(SQUARE(screen.w) + SQUARE(screen.h)); } void @@ -134,21 +160,14 @@ ui_print(int x, int y, Color col, char *fmt, ...) { void ui_title(char *fmt, ...) { - static char *last = NULL; char *title; va_list ap; va_start(ap, fmt); title = vsmprintf(fmt, ap); va_end(ap); - - if (!streq(title, last)) { - SetWindowTitle(title); - free(last); - last = title; - } else { - free(title); - } + SetWindowTitle(title); + free(title); } int @@ -165,6 +184,43 @@ ui_collides(Rect rect, Vector2 point) { return 0; } +int +ui_onscreen(Vector2 point) { + return ui_collides(screen.rect, point); +} + +int +ui_ring_rect_collides(Vector2 centre, float r, Rect rect) { + Vector2 rcent = {rect.x + rect.w / 2, rect.y + rect.h / 2}; + Vector2 d; + + d.x = fabsf(centre.x - rcent.x); + d.y = fabsf(centre.y - rcent.y); + + if (d.x > (rect.w / 2 + r)) return 0; + if (d.y > (rect.h / 2 + r)) return 0; + + if (d.x <= (rect.w / 2)) return 1; + if (d.y <= (rect.h / 2)) return 1; + + return ((d.x - rect.w / 2) * (d.x - rect.w / 2) + + (d.y - rect.h / 2) * (d.y - rect.h / 2)) <= r * r; +} + +int +ui_onscreen_ring(Vector2 centre, float r) { + float d = ui_vectordist(centre, screen.centre); + + if (d + screen.diag / 2 < r) + return 0; + return CheckCollisionCircleRec(centre, r, RLIFY_RECT(screen.rect)); +} + +int +ui_onscreen_circle(Vector2 centre, float r) { + return CheckCollisionCircleRec(centre, r, RLIFY_RECT(screen.rect)); +} + void ui_clickable_register(int x, int y, int w, int h, enum UiElements type, void *elem) { int i; @@ -225,11 +281,12 @@ ui_clickable_handle(Vector2 mouse, MouseButton button, Clickable *clickable) { } } -void +int ui_clickable_update(void) { Vector2 mouse; MouseButton button; int i; + int ret = 0; mouse = GetMousePosition(); /* I wish there was a: int GetMouseButton(void) */ @@ -239,10 +296,20 @@ ui_clickable_update(void) { else button = -1; for (i = 0; i < CLICKABLE_MAX; i++) { - if (clickable[i].elem && ui_collides(clickable[i].geom, mouse)) + if (clickable[i].elem && ui_collides(clickable[i].geom, mouse)) { ui_clickable_handle(mouse, button, &clickable[i]); - clickable[i].elem = NULL; + ret = 1; + } } + + return ret; +} + +void +ui_clickable_clear(void) { + int i; + for (i = 0; i < CLICKABLE_MAX; i++) + clickable[i].elem = NULL; } void @@ -260,6 +327,42 @@ ui_draw_border(int x, int y, int w, int h, int px) { DrawRectangle(x + w - px, y, px, h, COL_BORDER); /* right */ } +#define SEGMAX 2500 + +void +ui_draw_ring(int x, int y, float r, Color col) { + Vector2 v = {x, y}; + Polar p; + float s; + float prec = screen.diag * 2 / (PI * 2 * r) * 360; + float sdeg = 0, edeg = 360; + float deg; + + if (!ui_onscreen_ring(v, r)) + return; + + p = sys_polarize_around(v, screen.centre); + deg = p.theta * RAD2DEG; + + /* Draw the section of the ring (+ wriggle room) that will be onscreen + * be (start/end)ing prec degrees before/after the screen's centre + * relative to the ring's centre. */ + sdeg = deg + prec; + edeg = deg - prec; + + if (r < 100) + s = r; + else if (r < SEGMAX) + s = r / log10(r); + else + s = SEGMAX; + + /* Less segments are needed if there is less ring to place them in. */ + s *= (sdeg - edeg) / 360; + + DrawRing(v, r - 1, r, sdeg, edeg, s, col); +} + void ui_draw_tabs(int x, int y, int w, int h, Tabs *tabs) { int fw, fn, ftabw; @@ -328,9 +431,11 @@ ui_draw_checkbox(int x, int y, Checkbox *box) { w = h = FONT_SIZE; ui_draw_border(x, y, w, h, 1); - DrawRectangle(x + 1, y + 1, w - 2, h - 2, box->val ? COL_FG : COL_BG); + DrawRectangle(x + 1, y + 1, w - 2, h - 2, + box->enabled ? (box->val ? COL_FG : COL_BG) : COL_BORDER); ui_print(x + w + (w / 2), y + (h / 6), COL_FG, "%s", box->label); - ui_clickable_register(x, y, w + (w / 2) + ui_textsize(box->label), h, UI_CHECKBOX, box); + if (box->enabled) + ui_clickable_register(x, y, w + (w / 2) + ui_textsize(box->label), h, UI_CHECKBOX, box); } Vector2 @@ -373,19 +478,27 @@ ui_draw_tabbed_window(int x, int y, int w, int h, Tabs *tabs) { ui_draw_border(x, y, w, h, 2); } -void -ui_handle_view_main(void) { +int +ui_handle_view_main(int nowsel) { Vector2 mouse = GetMousePosition(); Vector2 delta = GetMouseDelta(); float wheel = GetMouseWheelMove(); float diff; - int i; + Body *furth; + + if (view_main.sys) + furth = view_main.sys->furthest_body; #define SCROLL_DIVISOR 10 - diff = wheel * (view_main.kmperpx/SCROLL_DIVISOR); - view_main.kmperpx -= diff; - view_main.kmx += (mouse.x - GetScreenWidth() / 2) * diff; - view_main.kmy += (mouse.y - GetScreenHeight() / 2) * diff; + if (wheel) { + diff = wheel * (view_main.kmperpx/SCROLL_DIVISOR); + if (diff > 0 || !furth || view_main.kmperpx * GetScreenHeight() < + 2 * (furth->type == BODY_COMET ? furth->maxdist : furth->dist)) { + view_main.kmperpx -= diff; + view_main.kmx += (mouse.x - GetScreenWidth() / 2) * diff; + view_main.kmy += (mouse.y - GetScreenHeight() / 2) * diff; + } + } if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { if (view_main.pan) { @@ -394,77 +507,134 @@ ui_handle_view_main(void) { } else if (!ui_collides(view_main.infobox.geom, mouse)) { view_main.pan = 1; } - } else view_main.pan = 0; + } else { + view_main.pan = 0; + } if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT) && !view_main.ruler.held && !ui_collides(view_main.infobox.geom, mouse)) { view_main.ruler.held = 1; view_main.ruler.origin = ui_pxtokm(mouse); - } else if (IsMouseButtonUp(MOUSE_BUTTON_RIGHT)) + } else if (IsMouseButtonUp(MOUSE_BUTTON_RIGHT)) { view_main.ruler.held = 0; + } - if (view_main.system != save->system) { - view_main.system = save->system; - - ui_title("Tactical: %s", save->system->name); + if (!view_main.sys) { + view_main.sys = sys_default(); } + if (nowsel) + ui_title("Tactical: %s", view_main.sys->name); + + view_main.infobox.names.dwarfn.enabled = view_main.infobox.names.dwarf.val; + view_main.infobox.names.asteroidn.enabled = view_main.infobox.names.asteroid.val; + + /* so that the debug stuff prints */ + if (delta.x || delta.y) + return 1; + return 0; } -void -ui_handle_view_colonies(void) { - ui_title("Colonies"); +int +ui_handle_view_colonies(int nowsel) { + if (nowsel) + ui_title("Colonies"); + return 1; } -void -ui_handle_view_fleets(void) { - ui_title("Fleets"); +int +ui_handle_view_fleets(int nowsel) { + if (nowsel) + ui_title("Fleets"); + return 1; } -void -ui_handle_view_design(void) { - ui_title("Design"); +int +ui_handle_view_design(int nowsel) { + if (nowsel) + ui_title("Design"); + return 1; } -void -ui_handle_view_sys(void) { - ui_title("Systems"); +int +ui_handle_view_sys(int nowsel) { + if (nowsel) + ui_title("Systems"); + view_sys.info.geom.h = GetScreenHeight() - VIEWS_HEIGHT; + if (!view_sys.sel) + view_sys.sel = sys_default(); + return 1; } -void -ui_handle_view_settings(void) { - ui_title("Settings"); +int +ui_handle_view_settings(int nowsel) { + if (nowsel) + ui_title("Settings"); + return 1; +} + +static int +ui_should_draw_body_checkbox(Body *body, int type, Checkbox *box) { + if ((body->type == type || (body->parent && + body->parent->type == type)) && + !box->val) + return 0; + return 1; } int ui_should_draw_body(Body *body, int orbit) { - if (!orbit && !ui_collides(SCREEN_RECT(), body->pxloc)) - return 0; - if (!orbit && body->type != BODY_STAR && - (body->type == BODY_COMET ? body->maxdist : body->dist) / view_main.kmperpx < ui_textsize(body->name)) - return 0; - if (!orbit && body->parent && body->type != BODY_STAR && - ui_vectordist(body->vector, body->parent->vector) < MIN_BODY_DIAM * view_main.kmperpx) - return 0; - if ((body->type == BODY_DWARF || (body->parent && body->parent->type == BODY_DWARF)) && - (!orbit || !view_main.infobox.orbit.dwarf.val) && - (orbit || !view_main.infobox.names.dwarf.val)) - return 0; - if ((body->type == BODY_ASTEROID || (body->parent && body->parent->type == BODY_ASTEROID)) && - (!orbit || !view_main.infobox.orbit.asteroid.val) && - (orbit || !view_main.infobox.names.asteroid.val)) - return 0; - if ((body->type == BODY_COMET || (body->parent && body->parent->type == BODY_COMET)) && - (!orbit || !view_main.infobox.orbit.comet.val) && - (orbit || !view_main.infobox.names.comet.val)) - return 0; + if (orbit) { + if (!body->parent) + return 0; + if (orbit < min_body_rad[body->parent->type]) + return 0; + if (!ui_should_draw_body_checkbox(body, BODY_DWARF, + &view_main.infobox.orbit.dwarf)) + return 0; + if (!ui_should_draw_body_checkbox(body, BODY_ASTEROID, + &view_main.infobox.orbit.asteroid)) + return 0; + if (!ui_should_draw_body_checkbox(body, BODY_COMET, + &view_main.infobox.orbit.comet)) + return 0; + } else { + if (!ui_onscreen(body->pxloc)) + return 0; + if (body->type != BODY_STAR && + (body->type == BODY_COMET ? body->maxdist : body->dist) + / view_main.kmperpx < ui_textsize(body->name)) + return 0; + if (body->parent && body->type != BODY_STAR && + ui_vectordist(body->vector, body->parent->vector) < + min_body_rad[body->type] * view_main.kmperpx) + return 0; + if (isdigit(*body->name) || *body->name == '(') { + if (!ui_should_draw_body_checkbox(body, BODY_DWARF, + &view_main.infobox.names.dwarfn)) + return 0; + if (!ui_should_draw_body_checkbox(body, BODY_ASTEROID, + &view_main.infobox.names.asteroidn)) + return 0; + } + if (!ui_should_draw_body_checkbox(body, BODY_DWARF, + &view_main.infobox.names.dwarf)) + return 0; + if (!ui_should_draw_body_checkbox(body, BODY_ASTEROID, + &view_main.infobox.names.asteroid)) + return 0; + if (!ui_should_draw_body_checkbox(body, BODY_COMET, + &view_main.infobox.names.comet)) + return 0; + } return 1; } void ui_draw_body(Body *body) { Vector2 parent; + float pxrad; int w; if (body->parent) { @@ -474,19 +644,24 @@ ui_draw_body(Body *body) { parent.y = 0; } - if (ui_should_draw_body(body, 1)) { - if (body->parent && body->type == BODY_COMET) - DrawLineV(parent, body->pxloc, COL_ORBIT); - else if (body->parent) - DrawCircleLines(parent.x, parent.y, - ui_vectordist(parent, body->pxloc), - COL_ORBIT); + if (body->parent) { + pxrad = ui_vectordist(parent, body->pxloc); + if (ui_should_draw_body(body, pxrad)) { + if (body->type == BODY_COMET) + DrawLineV(parent, body->pxloc, COL_ORBIT); + else + ui_draw_ring(parent.x, parent.y, pxrad, COL_ORBIT); + } } - if (body->radius / view_main.kmperpx > MIN_BODY_DIAM) + if (body->radius / view_main.kmperpx > min_body_rad[body->type]) w = body->radius / view_main.kmperpx; else - w = MIN_BODY_DIAM; - DrawCircle(body->pxloc.x, body->pxloc.y, w, COL_FG); + w = min_body_rad[body->type]; + DrawCircle(body->pxloc.x, body->pxloc.y, w, body_col[body->type]); + if (body->type == BODY_COMET && view_main.infobox.comettail.val && + 10 * view_main.kmperpx < body->curdist) + DrawLineV(body->pxloc, sys_vectorize_around(body->pxloc, + (Polar){11, body->theta} /* I have no fucking clue what is going on here. How does this end up correct, but not when I add 180? That theta isn't even pointing away from the sun!!! WHAT?!? */), COL_COMET); if (ui_should_draw_body(body, 0)) ui_print(body->pxloc.x + w + 2, body->pxloc.y + w + 2, COL_FG, "%s", body->name); @@ -497,24 +672,22 @@ ui_draw_view_main(void) { Vector2 mouse = GetMousePosition(); Vector2 mousekm = ui_pxtokm(mouse); Vector2 ruler; - Vector2 km; - Polar polar; Body *body; - float *massa; - size_t massi; float dist; size_t i; + float x, y; /* debug info */ - ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 10, COL_FG, "Xoff: %f | Yoff: %f | km/px: %f", + ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 10, COL_FG, "W: %f | H: %f", (float)screen.w, (float)screen.h); + ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 20, COL_FG, "Xoff: %f | Yoff: %f | km/px: %f", view_main.kmx, view_main.kmy, view_main.kmperpx); - ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 20, COL_FG, "X: %f | Y: %f", + ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 30, COL_FG, "X: %f | Y: %f", mousekm.x, mousekm.y); - ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 30, COL_FG, "FPS: %d (target: %d)", GetFPS(), TARGET_FPS); + ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 40, COL_FG, "FPS: %d (target: %d)", GetFPS(), TARGET_FPS); /* draw system bodies */ - for (i = 0; i < save->system->bodies_len; i++) { - body = save->system->bodies[i]; + for (i = 0; i < view_main.sys->bodies_len; i++) { + body = view_main.sys->bodies[i]; body->pxloc = ui_kmtopx(body->vector); ui_draw_body(body); } @@ -546,24 +719,17 @@ ui_draw_view_main(void) { ui_draw_tabbed_window(view_main.infobox.geom.x, view_main.infobox.geom.y, view_main.infobox.geom.w, view_main.infobox.geom.h, &view_main.infobox.tabs); - ui_draw_checkbox(view_main.infobox.geom.x + FONT_SIZE, - view_main.infobox.geom.y + WINDOW_TAB_HEIGHT + FONT_SIZE*1.5, - &view_main.infobox.names.dwarf); - ui_draw_checkbox(view_main.infobox.geom.x + FONT_SIZE, - view_main.infobox.geom.y + WINDOW_TAB_HEIGHT + FONT_SIZE*3, - &view_main.infobox.names.asteroid); - ui_draw_checkbox(view_main.infobox.geom.x + FONT_SIZE, - view_main.infobox.geom.y + WINDOW_TAB_HEIGHT + FONT_SIZE*4.5, - &view_main.infobox.names.comet); - ui_draw_checkbox(view_main.infobox.geom.x + FONT_SIZE, - view_main.infobox.geom.y + WINDOW_TAB_HEIGHT + FONT_SIZE*6, - &view_main.infobox.orbit.dwarf); - ui_draw_checkbox(view_main.infobox.geom.x + FONT_SIZE, - view_main.infobox.geom.y + WINDOW_TAB_HEIGHT + FONT_SIZE*7.5, - &view_main.infobox.orbit.asteroid); - ui_draw_checkbox(view_main.infobox.geom.x + FONT_SIZE, - view_main.infobox.geom.y + WINDOW_TAB_HEIGHT + FONT_SIZE*9, - &view_main.infobox.orbit.comet); + x = view_main.infobox.geom.x + FONT_SIZE; + y = view_main.infobox.geom.y + WINDOW_TAB_HEIGHT; + ui_draw_checkbox(x, y += FONT_SIZE*1.5, &view_main.infobox.names.dwarf); + ui_draw_checkbox(x, y += FONT_SIZE*1.5, &view_main.infobox.names.dwarfn); + ui_draw_checkbox(x, y += FONT_SIZE*1.5, &view_main.infobox.names.asteroid); + ui_draw_checkbox(x, y += FONT_SIZE*1.5, &view_main.infobox.names.asteroidn); + ui_draw_checkbox(x, y += FONT_SIZE*1.5, &view_main.infobox.names.comet); + ui_draw_checkbox(x, y += FONT_SIZE*1.5, &view_main.infobox.orbit.dwarf); + ui_draw_checkbox(x, y += FONT_SIZE*1.5, &view_main.infobox.orbit.asteroid); + ui_draw_checkbox(x, y += FONT_SIZE*1.5, &view_main.infobox.orbit.comet); + ui_draw_checkbox(x, y += FONT_SIZE*1.5, &view_main.infobox.comettail); } void @@ -590,8 +756,32 @@ ui_draw_view_design(void) { void ui_draw_view_sys(void) { - ui_print(10, GetScreenHeight() / 2, COL_FG, "System info/settings menu"); - ui_print(GetScreenWidth() / 2, GetScreenHeight() / 2, COL_FG, "Pannable system view"); + int x, y; + + x = view_sys.info.geom.x + view_sys.info.geom.w; + y = view_sys.info.geom.y; + + /* draw map */ + + /* draw divider */ + DrawLine(x, y, x, y + view_sys.info.geom.h, COL_BORDER); + + /* draw info */ + x = view_sys.info.geom.x + 10; + y += 10; + if (view_sys.sel) { + ui_print(x, y, COL_FG, "%s", view_sys.sel->name); + DrawLine(x, y + FONT_SIZE, x + view_sys.info.geom.w - 20, + y + FONT_SIZE, COL_BORDER); + y += 30; + ui_print(x, y, COL_FG, "Stars: %d", view_sys.sel->num.stars); + ui_print(x, y + 10, COL_FG, "Planets: %d", view_sys.sel->num.planets); + ui_print(x, y + 20, COL_FG, "Asteroids: %d", view_sys.sel->num.asteroids); + ui_print(x, y + 30, COL_FG, "Comets: %d", view_sys.sel->num.comets); + ui_print(x, y + 40, COL_FG, "Moons: %d", view_sys.sel->num.moons); + DrawLine(x, y + 52, x + 85, y + 52, COL_FG); + ui_print(x, y + 55, COL_FG, "Total: %d", view_sys.sel->bodies_len); + } } void diff --git a/src/views.h b/src/views.h @@ -0,0 +1,41 @@ +typedef struct { + struct { + Tabs tabs; + struct { + Checkbox dwarf; + Checkbox dwarfn; + Checkbox asteroid; + Checkbox asteroidn; + Checkbox comet; + } names; + struct { + Checkbox dwarf; + Checkbox asteroid; + Checkbox comet; + } orbit; + Checkbox comettail; + Rect geom; + } infobox; + int pan; + struct { + int held; + Vector2 origin; + } ruler; + float kmx, kmy; + float kmperpx; + struct { + int x, y; /* real y = GetScreenHeight() - y */ + int w, h; + } scale; + System *sys; +} View_main; + +typedef struct { + struct { + Rect geom; + } info; + int pan; + Vector2 off; + float lytopx; + System *sel; +} View_sys;