summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaweł Redman <pawel.redman@gmail.com>2016-03-30 08:25:04 +0200
committerPaweł Redman <pawel.redman@gmail.com>2016-03-30 08:25:04 +0200
commit4bf61f911194bb0b40d302df8d7dfff2fda19892 (patch)
tree1d8619d4cff047bf2a72aa6a1bcfe4e683f43226
Initial commit.
-rw-r--r--.gitignore2
-rw-r--r--Makefile36
-rw-r--r--TODO6
-rw-r--r--assets/font.pngbin0 -> 6090 bytes
-rw-r--r--assets/font.xcfbin0 -> 126726 bytes
-rw-r--r--src/common.h256
-rw-r--r--src/main.c90
-rw-r--r--src/physics.c244
-rw-r--r--src/renderer.c473
-rw-r--r--src/ui.c461
10 files changed, 1568 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..46ae590
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+cem
+obj
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..906910c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+CC = gcc
+CFLAGS += -g -Wall
+CPPFLAGS += -MMD
+LDFLAGS += -lm -lSDL2 -lSDL2_image
+
+PP_BOLD := $(shell tput bold)
+PP_RESET := $(shell tput sgr0)
+PP_CC := $(PP_BOLD)$(shell tput setf 6)CC$(PP_RESET)
+PP_LD := $(PP_BOLD)$(shell tput setf 2)LD$(PP_RESET)
+PP_RM := $(PP_BOLD)$(shell tput setf 4)RM$(PP_RESET)
+
+SRC := src/main.c src/physics.c src/renderer.c src/ui.c
+OBJ := $(SRC:src/%.c=obj/%.o)
+OUT := cem
+
+all: $(OUT)
+
+-include $(OBJ:.o=.d)
+
+obj/%.o : src/%.c
+ @echo "$(PP_CC) src/$*.c"
+ @mkdir -p $(@D)
+ @$(CC) $(CFLAGS) $(CPPFLAGS) -c src/$*.c -o obj/$*.o
+
+$(OUT): $(OBJ)
+ @echo "$(PP_LD) $(OUT)"
+ @$(CC) $(OBJ) -o $(OUT) $(LDFLAGS)
+
+clean:
+ @echo "${PP_RM} obj"
+ @rm -rf obj
+ @echo "${PP_RM} ${OUT}"
+ @rm -rf ${OUT}
+
+.PHONY: clean
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..88c901b
--- /dev/null
+++ b/TODO
@@ -0,0 +1,6 @@
+refactor
+ ui_window.ui_simview.selected -> *.select_valid
+ ui_window.ui_simview.selection -> *.select
+
+new shit:
+ phy_control and shit
diff --git a/assets/font.png b/assets/font.png
new file mode 100644
index 0000000..c597a38
--- /dev/null
+++ b/assets/font.png
Binary files differ
diff --git a/assets/font.xcf b/assets/font.xcf
new file mode 100644
index 0000000..fb54f78
--- /dev/null
+++ b/assets/font.xcf
Binary files differ
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..ee9c3fe
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,256 @@
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <math.h>
+#include <SDL2/SDL.h>
+
+#define PROGRAM_NAME "Symulator pól elektromagnetycznych"
+
+#include <stdio.h>
+#define con_printf printf
+
+#define max2(a, b) ((a) > (b) ? (a) : (b))
+#define max3(a, b, c) (max2(max2(a, b), c))
+#define min2(a, b) ((a) < (b) ? (a) : (b))
+#define min3(a, b, c) (min2(min2(a, b), c))
+
+// main
+
+char *va(const char *fmt, ...);
+int64_t get_time(void);
+
+// math
+
+typedef float vec_t;
+typedef vec_t vec2_t[2];
+typedef vec_t vec3_t[3];
+typedef vec_t vec4_t[4];
+typedef vec_t mst2_t[4];
+/* mst2_t is a 3x3 scale&translate matrix (only 4 components are needed):
+ * [ dw 0 dx ]
+ * [ 0 dh dy ]
+ * [ 0 0 1 ]
+ */
+
+static inline void v2_copy(vec_t *R, vec_t *A)
+{
+ R[0] = A[0];
+ R[1] = A[1];
+}
+
+static inline void v3_copy(vec_t *R, vec_t *A)
+{
+ R[0] = A[0];
+ R[1] = A[1];
+ R[2] = A[2];
+}
+
+static inline void v4_copy(vec_t *R, vec_t *A)
+{
+ R[0] = A[0];
+ R[1] = A[1];
+ R[2] = A[2];
+ R[3] = A[3];
+}
+
+static inline void v2_set(vec_t *R, vec_t a, vec_t b)
+{
+ R[0] = a;
+ R[1] = b;
+}
+
+static inline void v3_set(vec_t *R, vec_t a, vec_t b, vec_t c)
+{
+ R[0] = a;
+ R[1] = b;
+ R[2] = c;
+}
+
+static inline void v2_add(vec_t *R, vec_t *A, vec_t *B)
+{
+ R[0] = A[0] + B[0];
+ R[1] = A[1] + B[1];
+}
+
+static inline void v2_sub(vec_t *R, vec_t *A, vec_t *B)
+{
+ R[0] = A[0] - B[0];
+ R[1] = A[1] - B[1];
+}
+
+static inline void v2_mul(vec_t *R, vec_t *A, vec_t b)
+{
+ R[0] = A[0] * b;
+ R[1] = A[1] * b;
+}
+
+static inline void mst2_set_identity(mst2_t R)
+{
+ R[0] = 0;
+ R[1] = 0;
+ R[2] = 1;
+ R[3] = 1;
+}
+
+static inline void mst2_set(mst2_t R, vec_t dx, vec_t dy, vec_t dw, vec_t dh)
+{
+ R[0] = dx;
+ R[1] = dy;
+ R[2] = dw;
+ R[3] = dh;
+}
+
+static inline void mst2_inv(mst2_t R, mst2_t A)
+{
+ mst2_t copy = {A[0], A[1], A[2], A[3]};
+
+ R[0] = -copy[0] / copy[2];
+ R[1] = -copy[1] / copy[3];
+ R[2] = 1 / copy[2];
+ R[3] = 1 / copy[3];
+}
+
+static inline void v2_mul_mst2(vec_t *R, vec_t *A, mst2_t M)
+{
+ R[0] = M[0] + A[0] * M[2];
+ R[1] = M[1] + A[1] * M[3];
+}
+
+static inline void v2_div_mst2(vec_t *R, vec_t *A, mst2_t M)
+{
+ mst2_t inv;
+
+ mst2_inv(inv, M);
+ v2_mul_mst2(R, A, inv);
+}
+
+// v2_mul_mst2 but without translation
+static inline void v2_mul_mst2_nt(vec_t *R, vec_t *A, mst2_t M)
+{
+ R[0] = A[0] * M[2];
+ R[1] = A[1] * M[3];
+}
+
+// v2_div_mst2 but without translation
+static inline void v2_div_mst2_nt(vec_t *R, vec_t *A, mst2_t M)
+{
+ mst2_t inv;
+
+ mst2_inv(inv, M);
+ v2_mul_mst2_nt(R, A, inv);
+}
+
+static inline void mst2_dump(mst2_t M)
+{
+ printf("[%f\t%f\t%f]\n", M[2], 0.0, M[0]);
+ printf("[%f\t%f\t%f]\n", 0.0, M[3], M[1]);
+ printf("[%f\t%f\t%f]\n", 0.0, 0.0, 1.0);
+}
+
+// physics
+
+typedef struct {
+ size_t width, height, depth;
+ size_t zstr, ystr, xstr, size; // strides in no. of points (not bytes)
+ size_t zstr1, ystr1, xstr1, size1; // same as above but for associated
+ // scalar fields
+ float spacing; // in meters
+} phy_field_info;
+
+typedef struct {
+ float *E;
+ float *H;
+} phy_field_em;
+
+/*
+typedef struct {
+ int num;
+ void *data;
+} phy_command;
+*/
+
+typedef struct {
+ phy_field_info field_info;
+ phy_field_em fields[3];
+ float *field_eps, *field_mu; //permittivity and permeability
+ float time;
+
+ SDL_mutex *rotate_lock;
+} phy_sim;
+
+void phy_sim_destroy(phy_sim *sim);
+int phy_sim_create(phy_sim *sim);
+void phy_sim_compute_const_fields(phy_sim *sim);
+void phy_sim_step(phy_sim *sim, float dt);
+
+int phy_thread(phy_sim *sim);
+
+// renderer
+
+extern float r_color_white[4];
+extern float r_color_black[4];
+extern float r_color_red[4];
+extern float r_color_blue[4];
+
+typedef struct r_window_s r_window;
+struct r_window_s {
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ SDL_Texture *font_texture;
+
+ r_window *prev, *next;
+};
+
+int r_init(void);
+void r_quit(void);
+
+r_window *r_window_create(void);
+void r_window_destroy(r_window *rw);
+
+void r_clear(r_window *rw, float *color);
+void r_flip(r_window *rw);
+
+void r_clip_enable(r_window *rw, float x, float y, float w, float h);
+void r_clip_disable(r_window *rw);
+void r_draw_line(r_window *rw, float x0, float y0, float x1, float y1,
+ float *color);
+void r_draw_rect(r_window *rw, float x, float y, float w, float h,
+ float *color);
+
+#define TEXT_CENTERX 0x0001
+#define TEXT_RIGHTX 0x0002
+float r_text_width(float h, char *text);
+void r_draw_text(r_window *rw, float x, float y, float h, char *text,
+ float *color, int flags);
+
+typedef enum {
+ XSECTION_XY,
+ XSECTION_XZ,
+ XSECTION_YZ
+} r_xsection_type;
+
+typedef struct {
+ SDL_Texture *texture;
+ float aspect_ratio;
+} r_xsection;
+
+void r_xsection_create(r_xsection *xsection);
+void r_xsection_destroy(r_xsection *xsection);
+int r_xsection_update(r_window *rw, r_xsection *xsection,
+ phy_field_info *fi, float *field,
+ r_xsection_type type, float frac);
+void r_xsection_draw(r_window *rw, r_xsection *xsection,
+ float x, float y, float w, float h);
+
+// ui
+
+int ui_init(void);
+void ui_quit(void);
+
+void ui_renderer_window_register(r_window *rw);
+
+void ui_event(SDL_Event *event);
+void ui_draw(phy_sim *sim);
+
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..65a7ab3
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,90 @@
+#include "common.h"
+#include <signal.h>
+#include <time.h>
+
+char *va(const char *fmt, ...)
+{
+ va_list vl;
+ static char buffer[4096];
+
+ va_start(vl, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, vl);
+ va_end(vl);
+
+ return buffer;
+}
+
+int64_t get_time(void)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec * 1000000000 + ts.tv_nsec;
+}
+
+int main(void)
+{
+ int rv = 0;
+ r_window *first_window;
+ phy_sim sim;
+ SDL_Thread *sim_thread;
+
+ if (r_init()) {
+ con_printf("fatal error: renderer initialization failed\n");
+ rv = 1;
+ goto quit;
+ }
+
+ if (ui_init()) {
+ con_printf("fatal error: UI initialization failed\n");
+ rv = 1;
+ goto quit;
+ }
+
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+
+ first_window = r_window_create();
+ if (!first_window) {
+ con_printf("fatal error: couldn't open first window\n");
+ rv = 1;
+ goto quit;
+ }
+
+ ui_renderer_window_register(first_window);
+
+ if (phy_sim_create(&sim)) {
+ con_printf("fatal error: phy_sim_create failed\n");
+ rv = 1;
+ goto quit;
+ }
+
+ sim_thread = SDL_CreateThread((SDL_ThreadFunction)phy_thread,
+ "phy_thread", &sim);
+ if (!sim_thread) {
+ con_printf("fatal error: SDL_CreateThread failed: %s\n",
+ SDL_GetError());
+ rv = 1;
+ goto quit;
+ }
+
+ while (1) {
+ SDL_Event event;
+
+ while (SDL_PollEvent(&event)) {
+ if (event.type == SDL_QUIT)
+ goto quit;
+ else
+ ui_event(&event);
+ }
+
+ ui_draw(&sim);
+ }
+
+quit:
+ con_printf("main: exitting, rv=%i\n", rv);
+ phy_sim_destroy(&sim);
+ ui_quit();
+ r_quit();
+ SDL_Quit();
+ return rv;
+}
diff --git a/src/physics.c b/src/physics.c
new file mode 100644
index 0000000..be6a426
--- /dev/null
+++ b/src/physics.c
@@ -0,0 +1,244 @@
+#include "common.h"
+
+void phy_sim_destroy(phy_sim *sim) {
+ size_t i;
+
+ SDL_DestroyMutex(sim->rotate_lock);
+
+ for (i = 0; i < 3; i++) {
+ free(sim->fields[i].E);
+ free(sim->fields[i].H);
+ }
+}
+
+int phy_sim_create(phy_sim *sim) {
+ size_t i;
+ phy_field_info *fi;
+
+ memset(sim, 0, sizeof(phy_sim));
+
+ fi = &sim->field_info;
+
+ fi->width = 30;
+ fi->height = 30;
+ fi->depth = 30;
+ fi->spacing = 1.5e10 / max3(fi->width, fi->height, fi->depth);
+
+ fi->xstr = 3;
+ fi->ystr = fi->xstr * fi->width;
+ fi->zstr = fi->ystr * fi->height;
+ fi->size = fi->zstr * fi->depth;
+
+ fi->xstr1 = 1;
+ fi->ystr1 = fi->xstr1 * fi->width;
+ fi->zstr1 = fi->ystr1 * fi->height;
+ fi->size1 = fi->zstr1 * fi->depth;
+
+ for (i = 0; i < 3; i++) {
+ sim->fields[i].E = calloc(fi->size, sizeof(float));
+ sim->fields[i].H = calloc(fi->size, sizeof(float));
+ if (!sim->fields[i].E || !sim->fields[i].H) {
+ con_printf("phy_sim_create: out of memory\n");
+ goto error;
+ }
+ }
+
+ sim->field_eps = calloc(fi->size1, sizeof(float));
+ sim->field_mu = calloc(fi->size1, sizeof(float));
+
+ phy_sim_compute_const_fields(sim);
+
+ sim->rotate_lock = SDL_CreateMutex();
+ assert(sim->rotate_lock);
+
+ return 0;
+
+error:
+ phy_sim_destroy(sim);
+ return 1;
+}
+
+void phy_sim_compute_const_fields(phy_sim *sim)
+{
+ size_t x, y, z;
+ phy_field_info *fi = &sim->field_info;
+ size_t border_width;
+
+ border_width = 3;
+
+ for (z = 0; z < fi->depth; z++)
+ for (y = 0; y < fi->height; y++)
+ for (x = 0; x < fi->width; x++) {
+ float *eps, *mu;
+ float border_fx, border_fy, border_fz, border_f;
+
+ eps = sim->field_eps + z * fi->zstr1 + y * fi->ystr1 +
+ x * fi->xstr1;
+ mu = sim->field_mu + z * fi->zstr1 + y * fi->ystr1 +
+ x * fi->xstr1;
+
+ *eps = 1.129409066837282e+11;
+ *mu = -7.957747154594767e+5;
+
+/*
+ if (x < border_width) {
+ border_fx = (float)x / border_width;
+ } else if (x >= fi->width - border_width) {
+ border_fx = 1.0f - ((float)x - fi->width + border_width + 1) / border_width;
+ } else {
+ border_fx = 1.0f;
+ }
+
+ if (y < border_width) {
+ border_fy = (float)y / border_width;
+ } else if (y >= fi->height - border_width) {
+ border_fy = 1.0f - ((float)y - fi->height + border_width + 1) / border_width;
+ } else {
+ border_fy = 1.0f;
+ }
+
+ if (z < border_width) {
+ border_fz = (float)y / border_width;
+ } else if (z >= fi->depth - border_width) {
+ border_fz = 1.0f - ((float)z - fi->depth + border_width + 1) / border_width;
+ } else {
+ border_fz = 1.0f;
+ }
+
+ border_f = min3(border_fx, border_fy, border_fz);
+
+ *eps *= border_f;
+ *mu *= border_f;*/
+ }
+}
+
+void phy_sim_step_1(phy_field_info *fi, float dt, float *mul_const,
+ const float *L0, float *L2, const float *R)
+{
+ size_t x, y, z;
+
+#define AT(F, x, y, z) ((F) + (z) * fi->zstr + (y) * fi->ystr + (x) * fi->xstr)
+#define AT1(F, x, y, z) ((F)[(z) * fi->zstr1 + (y) * fi->ystr1 + \
+ (x) * fi->xstr1])
+
+ // X
+ for (z = 1; z < fi->depth - 1; z++)
+ for (y = 1; y < fi->height - 1; y++)
+ for (x = 0; x < fi->width; x++) {
+ float dRzdy, dRydz, dLxdt;
+
+ dRzdy = AT(R, x, y + 1, z)[2] - AT(R, x, y - 1, z)[2];
+ dRzdy /= 2 * fi->spacing;
+
+ dRydz = AT(R, x, y, z + 1)[1] - AT(R, x, y, z - 1)[1];
+ dRydz /= 2 * fi->spacing;
+
+ dLxdt = AT1(mul_const, x, y, z) * (dRzdy - dRydz);
+
+ AT(L2, x, y, z)[0] = AT(L0, x, y, z)[0] + dLxdt * dt;
+ }
+
+ // Y
+ for (z = 1; z < fi->depth - 1; z++)
+ for (y = 0; y < fi->height; y++)
+ for (x = 1; x < fi->width - 1; x++) {
+ float dRxdz, dRzdx, dLydt;
+
+ dRxdz = AT(R, x, y, z + 1)[0] - AT(R, x, y, z - 1)[0];
+ dRxdz /= 2 * fi->spacing;
+
+ dRzdx = AT(R, x + 1, y, z)[2] - AT(R, x - 1, y, z)[2];
+ dRzdx /= 2 * fi->spacing;
+
+ dLydt = AT1(mul_const, x, y, z) * (dRxdz - dRzdx);
+
+ AT(L2, x, y, z)[1] = AT(L0, x, y, z)[1] + dLydt * dt;
+ }
+
+ // Z
+ for (z = 0; z < fi->depth; z++)
+ for (y = 1; y < fi->height - 1; y++)
+ for (x = 1; x < fi->width - 1; x++) {
+ float dRydx, dRxdy, dLzdt;
+
+ dRydx = AT(R, x + 1, y, z)[1] - AT(R, x - 1, y, z)[1];
+ dRydx /= 2 * fi->spacing;
+
+ dRxdy = AT(R, x, y + 1, z)[0] - AT(R, x, y - 1, z)[0];
+ dRxdy /= 2 * fi->spacing;
+
+ dLzdt = AT1(mul_const, x, y, z) * (dRydx - dRxdy);
+
+ AT(L2, x, y, z)[2] = AT(L0, x, y, z)[2] + dLzdt * dt;
+ }
+#undef AT
+#undef AT1
+}
+
+void phy_sim_add_sources(phy_sim *sim)
+{
+ phy_field_info *fi = &sim->field_info;
+ size_t x, y, z;
+
+ for (z = 0; z < fi->depth; z++)
+ for (y = 0; y < fi->height; y++)
+ for (x = 0; x < fi->width; x++) {
+ float *E, *H, fx, fy, fz, chuj;
+
+ fx = (float)x / fi->width * 2 - 1;
+ fy = (float)y / fi->height * 2 - 1;
+ fz = (float)z / fi->depth * 2 - 1;
+
+ E = sim->fields[2].E + z * fi->zstr + y * fi->ystr + x * fi->xstr;
+ H = sim->fields[2].H + z * fi->zstr + y * fi->ystr + x * fi->xstr;
+
+ chuj = (0.3 - sqrt(fx * fx + fy * fy + fz * fz)) / 0.3;
+ if (chuj < 0)
+ chuj = 0;
+
+ chuj *= sin(sim->time) * 10;
+ //chuj *= exp(-10.0f * pow(sim->time - 0.5, 2)) * 2;
+ E[0] += chuj;
+ }
+}
+
+void phy_sim_step(phy_sim *sim, float dt) {
+ phy_field_info *fi = &sim->field_info;
+ phy_field_em *fields = sim->fields;
+
+ size_t i, size_in_bytes;
+
+ phy_sim_add_sources(sim);
+
+ size_in_bytes = fi->size * sizeof(float);
+
+ SDL_mutexP(sim->rotate_lock);
+ for (i = 0; i < 2; i++) {
+ // TODO: avoid copying, just switch around pointers
+ memcpy(fields[i].E, fields[i + 1].E, size_in_bytes);
+ memcpy(fields[i].H, fields[i + 1].H, size_in_bytes);
+ }
+ SDL_mutexV(sim->rotate_lock);
+
+ phy_sim_step_1(fi, dt, sim->field_eps,
+ fields[0].E, fields[2].E, fields[1].H);
+ phy_sim_step_1(fi, dt, sim->field_mu,
+ fields[0].H, fields[2].H, fields[1].E);
+
+ sim->time += dt;
+}
+
+/*
+void phy_control(phy_command cmd, void *data)
+
+} */
+
+int phy_thread(phy_sim *sim)
+{
+ while (1) {
+ phy_sim_step(sim, 0.1);
+ }
+
+ printf("phy_thread: quitting\n");
+}
+
diff --git a/src/renderer.c b/src/renderer.c
new file mode 100644
index 0000000..11c3706
--- /dev/null
+++ b/src/renderer.c
@@ -0,0 +1,473 @@
+#define _RENDERER_C
+#include "common.h"
+#include <SDL2/SDL_image.h>
+
+#define FONT_FILE "assets/font.png"
+
+float r_color_white[4] = {1, 1, 1, 1};
+float r_color_black[4] = {0, 0, 0, 1};
+float r_color_red[4] = {1, 0, 0, 1};
+float r_color_blue[4] = {0, 0, 1, 1};
+
+static struct {
+ SDL_Surface *font;
+ float font_aspect_ratio;
+
+ r_window *windows;
+} rs;
+
+int r_init(void)
+{
+ memset(&rs, 0, sizeof(rs));
+
+ con_printf("r_init: initializing SDL...\n");
+ if (SDL_Init(SDL_INIT_VIDEO) == -1) {
+ con_printf("r_init: SDL_Init(SDL_INIT_VIDEO) failed: %s\n",
+ SDL_GetError());
+ goto error;
+ }
+
+ con_printf("r_init: initializing SDL_image...\n");
+ if (IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG) !=
+ (IMG_INIT_JPG | IMG_INIT_PNG)) {
+ con_printf("r_init: IMG_Init failed: %s\n", SDL_GetError());
+ goto error;
+ }
+
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
+
+ con_printf("r_init: loading font \""FONT_FILE"\"...\n");
+ rs.font = IMG_Load("assets/font.png");
+ if (!rs.font) {
+ con_printf("r_init: IMG_Load failed: %s\n",
+ IMG_GetError());
+ goto error;
+ }
+
+ rs.font_aspect_ratio = (float)rs.font->w / rs.font->h;
+
+ con_printf("r_init: renderer init complete\n");
+ return 0;
+
+error:
+ r_quit();
+ return 1;
+}
+
+r_window *r_window_create(void)
+{
+ r_window *rw;
+
+ rw = calloc(1, sizeof(r_window));
+ if (!rw)
+ abort();
+
+ rw->prev = NULL;
+ if (rs.windows)
+ rs.windows->prev = rw;
+ rw->next = rs.windows;
+ rs.windows = rw;
+
+ rw->window = SDL_CreateWindow(PROGRAM_NAME, SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED, 300, 300,
+ SDL_WINDOW_RESIZABLE);
+ if (!rw->window) {
+ con_printf("r_window_create: SDL_CreateWindow failed: %s\n",
+ SDL_GetError());
+ goto error;
+ }
+
+ rw->renderer = SDL_CreateRenderer(rw->window, -1,
+ SDL_RENDERER_ACCELERATED |
+ SDL_RENDERER_PRESENTVSYNC);
+ if (!rw->renderer) {
+ con_printf("r_window_create: SDL_CreateRenderer failed: %s\n",
+ SDL_GetError());
+ goto error;
+ }
+
+ SDL_SetRenderDrawBlendMode(rw->renderer, SDL_BLENDMODE_BLEND);
+
+ rw->font_texture = SDL_CreateTextureFromSurface(rw->renderer, rs.font);
+ if (!rw->font_texture) {
+ con_printf("r_window_create: SDL_CreateTextureFromSurface"
+ " failed: %s\n", SDL_GetError());
+ goto error;
+ }
+
+ con_printf("r_window_create: created window #%i\n",
+ SDL_GetWindowID(rw->window));
+
+ return rw;
+
+error:
+ r_window_destroy(rw);
+ return NULL;
+}
+
+void r_window_destroy(r_window *rw)
+{
+ SDL_DestroyTexture(rw->font_texture);
+ SDL_DestroyRenderer(rw->renderer);
+ SDL_DestroyWindow(rw->window);
+
+ if (rw->prev)
+ rw->prev->next = rw->next;
+ if (rw->next)
+ rw->next->prev = rw->prev;
+
+ if (rs.windows == rw)
+ rs.windows = rw->next;
+
+ free(rw);
+}
+
+
+void r_quit(void)
+{
+ con_printf("r_quit: renderer is quitting\n");
+
+ while (rs.windows)
+ r_window_destroy(rs.windows);
+
+ SDL_FreeSurface(rs.font);
+ IMG_Quit();
+}
+
+static void r_set_color(SDL_Renderer *renderer, float *color)
+{
+ SDL_SetRenderDrawColor(renderer, color[0] * 255, color[1] * 255,
+ color[2] * 255, color[3] * 255);
+}
+
+
+void r_clear(r_window *rw, float *color)
+{
+ r_set_color(rw->renderer, color);
+ SDL_RenderClear(rw->renderer);
+}
+
+void r_flip(r_window *rw)
+{
+ SDL_RenderPresent(rw->renderer);
+}
+
+void r_clip_enable(r_window *rw, float x, float y, float w, float h)
+{
+ SDL_Rect rect = {x, y, w, h};
+ SDL_RenderSetClipRect(rw->renderer, &rect);
+}
+
+void r_clip_disable(r_window *rw)
+{
+ SDL_RenderSetClipRect(rw->renderer, NULL);
+}
+
+void r_draw_line(r_window *rw, float x0, float y0, float x1, float y1,
+ float *color)
+{
+
+ r_set_color(rw->renderer, color);
+ SDL_RenderDrawLine(rw->renderer, x0, y0, x1, y1);
+}
+
+void r_draw_rect(r_window *rw, float x, float y, float w, float h, float *color)
+{
+ SDL_Rect rect = {x, y, w, h};
+ r_set_color(rw->renderer, color);
+ SDL_RenderFillRect(rw->renderer, &rect);
+}
+
+static int r_iterate_chars(char **p)
+{
+ int rv;
+
+ if ((*p)[0] == '\0') {
+ return -1;
+ } else if (((*p)[0] & 0b10000000) == 0b00000000) {
+ rv = (*p)[0];
+ (*p)++;
+ return rv;
+ } else if (((*p)[0] & 0b11100000) == 0b11000000) {
+ if ((*p)[1] == '\0')
+ return -1;
+
+ rv = (((*p)[0] & 0b00011111) << 6) | ((*p)[1] & 0b00111111);
+ (*p) += 2;
+ return rv;
+ } else {
+ (*p)++;
+ return 0;
+ }
+}
+
+static int r_font_find_glyph(int ch)
+{
+ if (ch < 128)
+ return ch;
+
+ switch (ch) {
+ case 0x105: return 128; // ą
+ case 0xE4: return 129; // ä
+ case 0x107: return 130; // ć
+ case 0x119: return 131; // ę
+ case 0xEB: return 132; // ë
+ case 0x142: return 133; // ł
+ case 0x144: return 134; // ń
+ case 0xF3: return 135; // ó
+ case 0xF6: return 136; // ö
+ case 0x15B: return 137; // ś
+ case 0xFC: return 138; // ü
+ case 0x17C: return 139; // ż
+ case 0x17A: return 140; // ź
+ //case 0x: return 141; // free
+ //case 0x: return 142; // free
+ //case 0x: return 143; // free
+ case 0x104: return 144; // Ą
+ case 0xC4: return 145; // Ä
+ case 0x106: return 146; // Ć
+ case 0x118: return 147; // Ę
+ case 0xCB: return 148; // Ë
+ case 0x141: return 149; // Ł
+ case 0x143: return 150; // Ń
+ case 0xD3: return 151; // Ó
+ case 0xD6: return 152; // Ö
+ case 0x15A: return 153; // Ś
+ case 0xDC: return 154; // Ü
+ case 0x17B: return 155; // Ż
+ case 0x179: return 156; // Ź
+ //case 0x: return 157; // free
+ //case 0x: return 158; // free
+ //case 0x: return 159; // free
+ }
+
+ // lowercase greek
+ if (ch >= 0x3B1 && ch <= 0x3C1)
+ return ch - 0x3B1 + 160;
+ else if (ch >= 0x3C3 && ch <= 0x3C9)
+ return ch - 0x3C3 + 177;
+
+ // uppercase greek
+ if (ch >= 0x391 && ch <= 0x3A1)
+ return ch - 0x391 + 192;
+ else if (ch >= 0x3A3 && ch <= 0x3A9)
+ return ch - 0x3A3 + 209;
+
+ return 0;
+}
+
+float r_text_width(float h, char *text)
+{
+ char *p = text;
+ float rv = 0;
+
+ while(r_iterate_chars(&p) != -1)
+ rv += h * rs.font_aspect_ratio;
+
+ return rv;
+}
+
+void r_draw_text(r_window *rw, float x, float y, float h, char *text,
+ float *color, int flags)
+{
+ char *p = text;
+ float w = h * rs.font_aspect_ratio;
+ int ch;
+ float total_width;
+
+ if (flags & (TEXT_CENTERX | TEXT_RIGHTX)) {
+ total_width = r_text_width(h, text);
+
+ if (flags & TEXT_CENTERX)
+ x -= total_width / 2;
+ else
+ x -= total_width;
+ }
+
+ SDL_SetTextureColorMod(rw->font_texture, color[0] * 255, color[1] * 255,
+ color[2] * 255);
+ SDL_SetTextureAlphaMod(rw->font_texture, color[3] * 255);
+
+ while ((ch = r_iterate_chars(&p)) != -1) {
+ int glyph, glyph_x, glyph_y;
+ SDL_Rect src, dst;
+
+ glyph = r_font_find_glyph(ch);
+ glyph_x = glyph % 16;
+ glyph_y = glyph / 16;
+ src.x = rs.font->w / 16 * glyph_x;
+ src.y = rs.font->h / 16 * glyph_y;
+ src.w = rs.font->w / 16;
+ src.h = rs.font->h / 16;
+
+ dst.x = x;
+ dst.y = y;
+ dst.w = w;
+ dst.h = h;
+
+ SDL_RenderCopy(rw->renderer, rw->font_texture, &src, &dst);
+
+ x += w;
+ }
+}
+
+static uint8_t r_float_to_u8(float num)
+{
+ if (num >= 1.0f)
+ return 255;
+ else if (num <= -1.0f)
+ return 0;
+ else
+ return floor((num + 1.0f) * 0.5f * 256.0f);
+}
+
+static int r_xsection_alloc(SDL_Renderer *renderer, SDL_Texture **texture,
+ size_t width, size_t height)
+{
+ int old_width, old_height;
+
+ if (*texture) {
+ SDL_QueryTexture(*texture, NULL, NULL, &old_width, &old_height);
+
+ if (old_width != width || old_height != height) {
+ SDL_DestroyTexture(*texture);
+ *texture = NULL;
+ }
+ }
+
+ if (!*texture) {
+ *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGR888,
+ SDL_TEXTUREACCESS_STREAMING,
+ width, height);
+ if (!*texture) {
+ con_printf("r_xsection_alloc: SDL_CreateTexture failed"
+ ": %s\n", SDL_GetError());
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void r_xsection_create(r_xsection *xsection)
+{
+ memset(xsection, 0, sizeof(r_xsection));
+}
+
+void r_xsection_destroy(r_xsection *xsection)
+{
+ SDL_DestroyTexture(xsection->texture);
+}
+
+int r_xsection_update(r_window *rw, r_xsection *xsection,
+ phy_field_info *fi, float *field,
+ r_xsection_type type, float frac)
+{
+ size_t width, height, x, y, z;
+ uint8_t *pixels;
+ int pitch;
+
+ switch (type) {
+ case XSECTION_XY:
+ width = fi->width;
+ height = fi->height;
+ break;
+
+ case XSECTION_XZ:
+ width = fi->width;
+ height = fi->depth;
+ break;
+
+ case XSECTION_YZ:
+ width = fi->height;
+ height = fi->depth;
+ break;
+ }
+
+ if (frac < 0.0f)
+ frac = 0.0f;
+ else if (frac > 1.0f)
+ frac = 1.0f;
+
+ if (r_xsection_alloc(rw->renderer, &xsection->texture,
+ width, height)) {
+ return 1;
+ }
+
+ SDL_LockTexture(xsection->texture, NULL, (void**)&pixels, &pitch);
+
+ switch (type) {
+ case XSECTION_XY:
+ z = frac * fi->depth;
+ if (z >= fi->depth)
+ z = fi->depth - 1;
+
+ for (y = 0; y < fi->height; y++)
+ for (x = 0; x < fi->width; x++) {
+ uint8_t *pixel;
+ float *point;
+
+ pixel = pixels + (y * pitch + x * 4);
+ point = field + z * fi->zstr + y * fi->ystr +
+ x * fi->xstr;
+
+ pixel[0] = r_float_to_u8(point[0]);
+ pixel[1] = r_float_to_u8(point[1]);
+ pixel[2] = r_float_to_u8(point[2]);
+ }
+ break;
+
+ case XSECTION_XZ:
+ y = frac * fi->height;
+ if (y >= fi->height)
+ y = fi->height - 1;
+
+ for (z = 0; z < fi->depth; z++)
+ for (x = 0; x < fi->width; x++) {
+ uint8_t *pixel;
+ float *point;
+
+ pixel = pixels + (z * pitch + x * 4);
+ point = field + z * fi->zstr + y * fi->ystr +
+ x * fi->xstr;
+
+ pixel[0] = r_float_to_u8(point[0]);
+ pixel[1] = r_float_to_u8(point[1]);
+ pixel[2] = r_float_to_u8(point[2]);
+ }
+ break;
+
+ case XSECTION_YZ:
+ x = frac * fi->width;
+ if (x >= fi->width)
+ x = fi->width - 1;
+
+ for (z = 0; z < fi->depth; z++)
+ for (y = 0; y < fi->height; y++) {
+ uint8_t *pixel;
+ float *point;
+
+ pixel = pixels + (z * pitch + y * 4);
+ point = field + z * fi->zstr + y * fi->ystr +
+ x * fi->xstr;
+
+ pixel[0] = r_float_to_u8(point[0]);
+ pixel[1] = r_float_to_u8(point[1]);
+ pixel[2] = r_float_to_u8(point[2]);
+ }
+ break;
+ }
+
+ SDL_UnlockTexture(xsection->texture);
+
+ xsection->aspect_ratio = (float)width / height;
+
+ return 0;
+}
+
+void r_xsection_draw(r_window *rw, r_xsection *xsection,
+ float x, float y, float w, float h)
+{
+ SDL_Rect dst = {x, y, w, h};
+ SDL_RenderCopy(rw->renderer, xsection->texture, NULL, &dst);
+}
diff --git a/src/ui.c b/src/ui.c
new file mode 100644
index 0000000..0f4477b
--- /dev/null
+++ b/src/ui.c
@@ -0,0 +1,461 @@
+#include "common.h"
+
+struct {
+ float font_size;
+ float color_text[4];
+ float color_text_light[4];
+ float color_background[4];
+ float color_main[4];
+ float color_info[4];
+ float color_selection[4];
+} theme =
+{
+ .font_size = 20,
+ .color_text = {0, 0, 0, 1},
+ .color_text_light = {0.5, 0.5, 0.5, 1},
+ .color_background = {0.1, 0.1, 0.1, 1},
+ .color_main = {0.75, 0.75, 0.75, 1},
+ .color_info = {1, 1, 1, 1},
+ .color_selection = {1, 0, 0, 1}
+};
+
+#define MAX_INFO 1024
+
+typedef struct {
+ bool dragging;
+ r_xsection_type xsection_type;
+ float xsection_frac;
+ bool selected;
+ vec3_t selection;
+
+ bool info_valid;
+ int64_t info_time;
+ char info[MAX_INFO];
+
+ float margin_bottom, margin_top;
+
+ mst2_t origin, scale; // in virtual space
+ mst2_t tf_v2s, tf_x2s;
+ r_xsection xsection;
+} ui_simview;
+
+typedef enum {
+ UI_WINDOW_SIMVIEW
+} ui_window_type;
+
+typedef struct ui_window_s ui_window;
+struct ui_window_s {
+ int w, h;
+ r_window *rw;
+ ui_window *prev, *next;
+
+ bool initialized;
+ int64_t last_frame;
+ size_t frame_count;
+ vec2_t mouse;
+
+ ui_window_type type;
+ ui_simview simview;
+};
+
+static struct {
+ ui_window *windows;
+
+ bool use_window_open_timer;
+ int64_t window_open_timer;
+} uis;
+
+
+int ui_init(void)
+{
+ return 0;
+}
+
+static void ui_window_destroy(ui_window *uiw)
+{
+ con_printf("ui_window_destroy: destroying uiw=%p\n", uiw);
+
+ if (uiw->prev)
+ uiw->prev->next = uiw->next;
+ if (uiw->next)
+ uiw->next->prev = uiw->prev;
+
+ if (uis.windows == uiw)
+ uis.windows = uiw->next;
+
+ r_xsection_destroy(&uiw->simview.xsection);
+
+ free(uiw);
+}
+
+void ui_quit(void)
+{
+ while (uis.windows)
+ ui_window_destroy(uis.windows);
+}
+
+void ui_renderer_window_register(r_window *rw)
+{
+ ui_window *uiw;
+
+ uiw = calloc(1, sizeof(ui_window));
+ if (!uiw)
+ abort();
+
+ uiw->prev = NULL;
+ if (uis.windows)
+ uis.windows->prev = uiw;
+ uiw->next = uis.windows;
+ uis.windows = uiw;
+
+ uiw->rw = rw;
+
+ con_printf("ui_renderer_window_register: registered window #%i"
+ ", uiw = %p\n", SDL_GetWindowID(rw->window), uiw);
+
+ r_xsection_create(&uiw->simview.xsection);
+}
+
+static ui_window *ui_find_window(int id)
+{
+ ui_window *uiw;
+
+ for (uiw = uis.windows; uiw; uiw = uiw->next)
+ if (SDL_GetWindowID(uiw->rw->window) == id)
+ return uiw;
+
+ return NULL;
+}
+
+void ui_infof(ui_window *uiw, const char *fmt, ...)
+{
+ va_list vl;
+
+ va_start(vl, fmt);
+ vsnprintf(uiw->simview.info, MAX_INFO, fmt, vl);
+ va_end(vl);
+
+ uiw->simview.info_valid = true;
+ uiw->simview.info_time = get_time();
+}
+
+static void ui_simview_set_selection(ui_simview *sv, vec2_t sel_2d)
+{
+ if (sel_2d[0] < 0.0f || sel_2d[0] > 1.0f ||
+ sel_2d[1] < 0.0f || sel_2d[1] > 1.0f) {
+ sv->selected = false;
+ return;
+ }
+
+ switch (sv->xsection_type) {
+ case XSECTION_XY:
+ v3_set(sv->selection, sel_2d[0], sel_2d[1], sv->xsection_frac);
+ break;
+
+ case XSECTION_XZ:
+ v3_set(sv->selection, sel_2d[0], sv->xsection_frac, sel_2d[1]);
+ break;
+
+ case XSECTION_YZ:
+ v3_set(sv->selection, sv->xsection_frac, sel_2d[0], sel_2d[1]);
+ break;
+ }
+
+ sv->selected = true;
+}
+
+void ui_event_window(SDL_Event *event, ui_window *uiw)
+{
+ ui_simview *sv = &uiw->simview;
+
+ if (!uiw->initialized)
+ return;
+
+ switch (event->type) {
+ case SDL_MOUSEWHEEL:
+ {
+ vec2_t mouse_v;
+ float delta;
+
+ v2_div_mst2(mouse_v, uiw->mouse, sv->tf_v2s);
+
+ delta = pow(1.1, event->wheel.y);
+
+ v2_mul(sv->scale, sv->scale, delta);
+
+ v2_sub(sv->origin, sv->origin, mouse_v);
+ v2_mul(sv->origin, sv->origin, delta);
+ v2_add(sv->origin, sv->origin, mouse_v);
+ }
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ if (event->button.button == SDL_BUTTON_LEFT &&
+ event->button.y > sv->margin_top &&
+ event->button.y < uiw->h - sv->margin_bottom)
+ sv->dragging = true;
+
+ if (event->button.button == SDL_BUTTON_RIGHT) {
+ vec2_t sel_2d;
+
+ v2_div_mst2(sel_2d, uiw->mouse, sv->tf_x2s);
+ ui_simview_set_selection(sv, sel_2d);
+ }
+ break;
+
+ case SDL_MOUSEBUTTONUP:
+ if (event->button.button == SDL_BUTTON_LEFT)
+ sv->dragging = false;
+ break;
+
+ case SDL_MOUSEMOTION:
+ uiw->mouse[0] = event->motion.x;
+ uiw->mouse[1] = event->motion.y;
+
+ if (sv->dragging) {
+ vec2_t delta_v;
+
+ v2_set(delta_v, event->motion.xrel, event->motion.yrel);
+ v2_div_mst2_nt(delta_v, delta_v, sv->tf_v2s);
+ v2_add(sv->origin, sv->origin, delta_v);
+ }
+ break;
+
+ case SDL_KEYDOWN:
+ switch (event->key.keysym.sym) {
+ case SDLK_1:
+ sv->xsection_type = XSECTION_XY;
+ break;
+
+ case SDLK_2:
+ sv->xsection_type = XSECTION_XZ;
+ break;
+
+ case SDLK_3:
+ sv->xsection_type = XSECTION_YZ;
+ break;
+
+ case SDLK_w:
+ sv->xsection_frac += 0.1f;
+ if (sv->xsection_frac > 1.0f)
+ sv->xsection_frac = 1.0f;
+ break;
+
+ case SDLK_s:
+ sv->xsection_frac -= 0.1f;
+ if (sv->xsection_frac < 0.0f)
+ sv->xsection_frac = 0.0f;
+ break;
+ }
+ }
+}
+
+void ui_event(SDL_Event *event)
+{
+ if (event->type == SDL_KEYDOWN &&
+ event->key.keysym.sym == SDLK_RETURN) {
+
+ if (!uis.use_window_open_timer ||
+ uis.window_open_timer + 500000000 <= get_time()) {
+ r_window *rw;
+
+ uis.use_window_open_timer = true;
+ uis.window_open_timer = get_time();
+
+ rw = r_window_create();
+ if (rw)
+ ui_renderer_window_register(rw);
+ }
+ } else if (event->type == SDL_WINDOWEVENT &&
+ event->window.event == SDL_WINDOWEVENT_CLOSE) {
+ ui_window *uiw;
+
+ uiw = ui_find_window(event->window.windowID);
+ if (!uiw)
+ con_printf("WARNING: ui_event: SDL_WINDOWEVENT_"
+ "CLOSE for a window not registered"
+ " by ui\n");
+ else {
+ r_window_destroy(uiw->rw);
+ ui_window_destroy(uiw);
+ }
+ } else {
+ ui_window *uiw;
+
+ // SDL sends some SDL_WINDOWEVENTs after a window's been closed
+
+ uiw = ui_find_window(event->window.windowID);
+ if (uiw)
+ ui_event_window(event, uiw);
+ }
+}
+
+void ui_animate_exp(float *val, float targ, float lambda, float dt)
+{
+ *val = targ + (*val - targ) * exp(-lambda * dt);
+}
+
+void ui_draw_window_simview(ui_window *uiw, phy_sim *sim, int64_t time,
+ float dt)
+{
+ ui_simview *sv = &uiw->simview;
+ float aspect_ratio;
+ vec2_t origin_s, scale_s;
+ phy_field_em *field_em = sim->fields + 2;
+
+ const char *xsection_type_strings[] = {
+ "Przekrój XY przez Z",
+ "Przekrój XZ przez Y",
+ "Przekrój YZ przez X"
+ };
+
+ // xsection
+
+ SDL_mutexP(sim->rotate_lock);
+ r_xsection_update(uiw->rw, &sv->xsection, &sim->field_info,
+ field_em->E, sv->xsection_type, sv->xsection_frac);
+ SDL_mutexV(sim->rotate_lock);
+
+ aspect_ratio = sv->xsection.aspect_ratio;
+
+ if (uiw->w > uiw->h * aspect_ratio)
+ mst2_set(sv->tf_v2s, (uiw->w - uiw->h * aspect_ratio) / 2.0f, 0,
+ uiw->h * aspect_ratio, uiw->h);
+ else
+ mst2_set(sv->tf_v2s, 0, (uiw->h - uiw->w / aspect_ratio) / 2.0f,
+ uiw->w, uiw->w / aspect_ratio);
+
+ v2_mul_mst2(origin_s, sv->origin, sv->tf_v2s);
+ v2_mul_mst2_nt(scale_s, sv->scale, sv->tf_v2s);
+
+ mst2_set(sv->tf_x2s, origin_s[0], origin_s[1],
+ scale_s[0], scale_s[1]);
+
+ r_xsection_draw(uiw->rw, &sv->xsection, origin_s[0], origin_s[1],
+ scale_s[0], scale_s[1]);
+
+ // xsection - selection
+
+ if (sv->selected)
+ {
+ vec2_t selection_s;
+
+ switch (sv->xsection_type) {
+ case XSECTION_XY:
+ v2_set(selection_s, sv->selection[0], sv->selection[1]);
+ break;
+
+ case XSECTION_XZ:
+ v2_set(selection_s, sv->selection[0], sv->selection[2]);
+ break;
+
+ case XSECTION_YZ:
+ v2_set(selection_s, sv->selection[1], sv->selection[2]);
+ break;
+ }
+
+ v2_mul_mst2(selection_s, selection_s, sv->tf_x2s);
+
+ r_draw_line(uiw->rw, selection_s[0], 0, selection_s[0], uiw->h,
+ theme.color_selection);
+ r_draw_line(uiw->rw, 0, selection_s[1], uiw->w, selection_s[1],
+ theme.color_selection);
+
+
+ r_draw_rect(uiw->rw, 0, uiw->h - theme.font_size,
+ uiw->w, theme.font_size, theme.color_main);
+
+ r_draw_text(uiw->rw, 0, uiw->h - theme.font_size,
+ theme.font_size,
+ va("x = [%f %f %f]", sv->selection[0],
+ sv->selection[1], sv->selection[2]),
+ theme.color_text, 0);
+
+ sv->margin_bottom = 1 * theme.font_size;
+ }
+ else
+ sv->margin_bottom = 0;
+
+ // status
+
+ r_draw_rect(uiw->rw, 0, 0, uiw->w, theme.font_size, theme.color_main);
+
+ r_draw_text(uiw->rw, uiw->w, 0, theme.font_size,
+ va("%.0fkl./s %04zu", 1.0f / dt, uiw->frame_count % 10000),
+ theme.color_text_light, TEXT_RIGHTX);
+
+ r_draw_text(uiw->rw, 0, 0, theme.font_size,
+ va("%s = %f", xsection_type_strings[sv->xsection_type],
+ sv->xsection_frac),
+ theme.color_text, 0);
+
+ // info text
+
+ if (sv->info_valid && sv->info_time + 2000000000ll > time)
+ {
+ vec4_t color;
+
+ if (sv->info_time + 1000000000ll >= time)
+ color[3] = 1.0f;
+ else
+ color[3] = 1.0f - (time - sv->info_time - 1000000000ll)
+ / 1.0e9f;
+
+ v3_copy(color, theme.color_info);
+
+ r_draw_rect(uiw->rw, 0, theme.font_size, uiw->w,
+ theme.font_size, color);
+
+ v3_copy(color, theme.color_text);
+
+ r_draw_text(uiw->rw, uiw->w / 2.0f, theme.font_size,
+ theme.font_size, sv->info, color, TEXT_CENTERX);
+ }
+}
+
+void ui_draw_window(ui_window *uiw, phy_sim *sim)
+{
+ ui_simview *sv = &uiw->simview;
+ int64_t time;
+ float dt;
+
+ time = get_time();
+
+ if (!uiw->initialized) {
+ uiw->initialized = true;
+ uiw->last_frame = time;
+
+ v2_set(sv->origin, 0, 0);
+ v2_set(sv->scale, 1, 1);
+ sv->xsection_type = XSECTION_XY;
+ sv->xsection_frac = 0.5f;
+
+ sv->selected = false;
+ sv->margin_top = theme.font_size;
+ sv->margin_bottom = 0;
+
+ sv->info_valid = false;
+
+ r_clear(uiw->rw, r_color_black);
+ return;
+ }
+
+ dt = (get_time() - uiw->last_frame) * 1.0e-9;
+
+ r_clear(uiw->rw, r_color_black);
+ ui_draw_window_simview(uiw, sim, time, dt);
+ r_flip(uiw->rw);
+
+ uiw->last_frame = time;
+}
+
+void ui_draw(phy_sim *sim)
+{
+ ui_window *uiw;
+
+ for (uiw = uis.windows; uiw; uiw = uiw->next) {
+ SDL_GetWindowSize(uiw->rw->window, &uiw->w, &uiw->h);
+ ui_draw_window(uiw, sim);
+ uiw->frame_count++;
+ }
+}