From 480852ff76c618cf78cea0c2e1f1ce5dc9509733 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sun, 21 Jan 2024 01:36:21 +1100 Subject: [PATCH] got something rendering --- CMakeLists.txt | 4 +- src/graphics/arrays.cpp | 56 ++++++++++ src/graphics/arrays.hpp | 20 ++++ src/graphics/font.cpp | 72 +++++++++++++ src/graphics/font.hpp | 20 ++++ src/graphics/keyboard.cpp | 24 +++++ src/graphics/keyboard.hpp | 10 ++ src/graphics/resize.cpp | 54 ++++++++++ src/graphics/resize.hpp | 11 ++ src/graphics/shader.cpp | 87 +++++++++++++++ src/graphics/shader.hpp | 10 ++ src/graphics/window.cpp | 89 ++++++++++++++++ src/graphics/window.hpp | 18 ++++ src/main.cpp | 213 ++----------------------------------- src/main.cpp.old | 216 ++++++++++++++++++++++++++++++++++++++ 15 files changed, 696 insertions(+), 208 deletions(-) create mode 100644 src/graphics/arrays.cpp create mode 100644 src/graphics/arrays.hpp create mode 100644 src/graphics/font.cpp create mode 100644 src/graphics/font.hpp create mode 100644 src/graphics/keyboard.cpp create mode 100644 src/graphics/keyboard.hpp create mode 100644 src/graphics/resize.cpp create mode 100644 src/graphics/resize.hpp create mode 100644 src/graphics/shader.cpp create mode 100644 src/graphics/shader.hpp create mode 100644 src/graphics/window.cpp create mode 100644 src/graphics/window.hpp create mode 100644 src/main.cpp.old diff --git a/CMakeLists.txt b/CMakeLists.txt index f1fad78..074a5de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,10 +3,10 @@ cmake_minimum_required(VERSION 3.25) project(FastNuclearSim VERSION 1.0) set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_FLAGS "-g -lncurses") +set(CMAKE_CXX_FLAGS "-g -lncurses -I/usr/include/freetype2") file(GLOB_RECURSE SOURCES src/*.cpp) add_executable(FastNuclearSim ${SOURCES}) -target_link_libraries(FastNuclearSim PUBLIC stdc++ m) +target_link_libraries(FastNuclearSim PUBLIC stdc++ m GLEW glfw GL freetype) diff --git a/src/graphics/arrays.cpp b/src/graphics/arrays.cpp new file mode 100644 index 0000000..62447f9 --- /dev/null +++ b/src/graphics/arrays.cpp @@ -0,0 +1,56 @@ + +#include +#include + +#include "arrays.hpp" +#include "font.hpp" + +using namespace sim::graphics; + +static unsigned int vao, vbo, ebo; + +static void* ptr_diff(void* a, void* b) +{ + return (void*)((size_t)a - (size_t)b); +} + +unsigned int arrays::init() +{ + vertex v; + unsigned long handle = font::chars['*'].handle; + + arrays::vertex vertices[4] = { + {handle, {0.0f, 1.0f}, {-0.5f, -0.5f, 0.0f}}, + {handle, {0.0f, 0.0f}, {-0.5f, 0.5f, 0.0f}}, + {handle, {1.0f, 1.0f}, { 0.5f, -0.5f, 0.0f}}, + {handle, {1.0f, 0.0f}, { 0.5f, 0.5f, 0.0f}} + }; + + int indices[6] = { + 0, 1, 3, 0, 2, 3 + }; + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glGenBuffers(1, &vbo); + glGenBuffers(1, &ebo); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); + + glVertexAttribLPointer(0, 1, GL_UNSIGNED_INT64_ARB, sizeof(v), ptr_diff(&v.texid, &v)); + glEnableVertexAttribArray(0); + + glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(v), ptr_diff(&v.texpos, &v)); + glEnableVertexAttribArray(1); + + glVertexAttribPointer(2, 3, GL_FLOAT, false, sizeof(v), ptr_diff(&v.pos, &v)); + glEnableVertexAttribArray(2); + + return vao; +} + diff --git a/src/graphics/arrays.hpp b/src/graphics/arrays.hpp new file mode 100644 index 0000000..14dba03 --- /dev/null +++ b/src/graphics/arrays.hpp @@ -0,0 +1,20 @@ + +#pragma once + +#include +#include + +namespace sim::graphics::arrays +{ + +struct vertex +{ + unsigned long texid; + glm::vec2 texpos; + glm::vec3 pos; +}; + +unsigned int init(); + +}; + diff --git a/src/graphics/font.cpp b/src/graphics/font.cpp new file mode 100644 index 0000000..73d59f9 --- /dev/null +++ b/src/graphics/font.cpp @@ -0,0 +1,72 @@ + +#include +#include +#include +#include FT_FREETYPE_H + +#include +#include + +#include "font.hpp" + +using namespace sim::graphics; + +font::character font::chars[128]; + +void font::init() +{ + FT_Library ft; + FT_Face face; + + if(FT_Init_FreeType(&ft)) + { + std::cout << "Error: failed to init freetype\n"; + return; + } + + if(FT_New_Face(ft, "/usr/share/fonts/noto/NotoSans-Regular.ttf", 0, &face)) + { + std::cout << "Error: failed to load freetype font\n"; + return; + } + + FT_Set_Pixel_Sizes(face, 0, 1024); + + GLuint texids[128]; + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + for(int i = 0; i < 128; i++) + { + if(FT_Load_Char(face, (char)i, FT_LOAD_RENDER)) + { + std::cout << "Error: failed to load glyph " << i << "\n"; + } + + character& c = chars[i]; + c.advance = face->glyph->advance.x; + c.size = {face->glyph->bitmap.width, face->glyph->bitmap.rows}; + c.bearing = {face->glyph->bitmap_left, face->glyph->bitmap_top}; + + if(c.size.x == 0 || c.size.y == 0) + { + c.handle = 0; + continue; + } + + glCreateTextures(GL_TEXTURE_2D, 1, &texids[i]); + glTextureStorage2D(texids[i], 1, GL_R8, c.size.x, c.size.y); + glTextureSubImage2D(texids[i], 0, 0, 0, c.size.x, c.size.y, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer); + + glTextureParameteri(texids[i], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(texids[i], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(texids[i], GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTextureParameteri(texids[i], GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + c.handle = glGetTextureHandleARB(texids[i]); + glMakeTextureHandleResidentARB(c.handle); + + chars[i] = c; + } +} + diff --git a/src/graphics/font.hpp b/src/graphics/font.hpp new file mode 100644 index 0000000..1665ff4 --- /dev/null +++ b/src/graphics/font.hpp @@ -0,0 +1,20 @@ + +#pragma once + +namespace sim::graphics::font +{ + +struct character +{ + unsigned long handle; + long advance; + glm::ivec2 size; + glm::ivec2 bearing; +}; + +void init(); + +extern character chars[128]; + +}; + diff --git a/src/graphics/keyboard.cpp b/src/graphics/keyboard.cpp new file mode 100644 index 0000000..efadd6c --- /dev/null +++ b/src/graphics/keyboard.cpp @@ -0,0 +1,24 @@ + +#include +#include + +#include "keyboard.hpp" +#include "window.hpp" +#include "resize.hpp" + +using namespace sim::graphics; + +static void cb_keypress(GLFWwindow* win, int key, int sc, int action, int mods) +{ + if(key == GLFW_KEY_F11 && action == GLFW_RELEASE) + { + resize::toggle_fullscreen(); + } +} + +void keyboard::init() +{ + GLFWwindow* win = window::get_window(); + glfwSetKeyCallback(win, cb_keypress); +} + diff --git a/src/graphics/keyboard.hpp b/src/graphics/keyboard.hpp new file mode 100644 index 0000000..6ad89db --- /dev/null +++ b/src/graphics/keyboard.hpp @@ -0,0 +1,10 @@ + +#pragma once + +namespace sim::graphics::keyboard +{ + +void init(); + +}; + diff --git a/src/graphics/resize.cpp b/src/graphics/resize.cpp new file mode 100644 index 0000000..20dda38 --- /dev/null +++ b/src/graphics/resize.cpp @@ -0,0 +1,54 @@ + +#include +#include + +#include "resize.hpp" +#include "window.hpp" + +using namespace sim::graphics; + +static bool is_fullscreen = false; + +static int win_w = 800; +static int win_h = 600; + +static int win_restore_w; +static int win_restore_h; +static int win_restore_x; +static int win_restore_y; + +void resize::toggle_fullscreen() +{ + GLFWwindow* win = window::get_window(); + is_fullscreen = !is_fullscreen; + + if(is_fullscreen) + { + win_restore_w = win_w; + win_restore_h = win_h; + + glfwGetWindowPos(win, &win_restore_x, &win_restore_y); + GLFWmonitor* monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode* mode = glfwGetVideoMode(monitor); + glfwSetWindowMonitor(win, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); + } + + else + { + glfwSetWindowMonitor(win, nullptr, win_restore_x, win_restore_y, win_restore_w, win_restore_h, 0); + } +} + +static void cb_framebuffer_size(GLFWwindow* win, int w, int h) +{ + win_w = w; + win_h = h; + glViewport(0, 0, w, h); +} + +void resize::init() +{ + GLFWwindow* win = window::get_window(); + glfwSetFramebufferSizeCallback(win, cb_framebuffer_size); +} + diff --git a/src/graphics/resize.hpp b/src/graphics/resize.hpp new file mode 100644 index 0000000..50e0eb1 --- /dev/null +++ b/src/graphics/resize.hpp @@ -0,0 +1,11 @@ + +#pragma once + +namespace sim::graphics::resize +{ + +void init(); +void toggle_fullscreen(); + +}; + diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp new file mode 100644 index 0000000..66a0e02 --- /dev/null +++ b/src/graphics/shader.cpp @@ -0,0 +1,87 @@ + +#include +#include + +#include + +#include "shader.hpp" +#include "window.hpp" + +using namespace sim::graphics; + +static const char* VERTEX_SHADER = R"( +#version 460 core +#extension GL_ARB_bindless_texture : require + +layout (location = 0) in sampler2D aTex; +layout (location = 1) in vec2 aTexPos; +layout (location = 2) in vec3 aPos; + +out flat sampler2D tex; +out vec2 texPos; + +void main() +{ + gl_Position = vec4(aPos, 1.0); + texPos = aTexPos; + tex = aTex; +} + +)"; + +static const char* FRAGMENT_SHADER = R"( +#version 460 core +#extension GL_ARB_bindless_texture : require + +in flat sampler2D tex; +in vec2 texPos; + +out vec4 FragColour; + +void main() +{ + FragColour = vec4(1) * texture2D(tex, texPos); +} + +)"; + +static unsigned int prog_id; + +static int load_shader(const char** src, int type) +{ + int id = glCreateShader(type); + + glShaderSource(id, 1, src, nullptr); + glCompileShader(id); + + return id; +} + +unsigned int shader::init_program() +{ + int success; + int vsh_id = load_shader(&VERTEX_SHADER, GL_VERTEX_SHADER); + int fsh_id = load_shader(&FRAGMENT_SHADER, GL_FRAGMENT_SHADER); + prog_id = glCreateProgram(); + + glAttachShader(prog_id, vsh_id); + glAttachShader(prog_id, fsh_id); + glLinkProgram(prog_id); + glGetProgramiv(prog_id, GL_LINK_STATUS, &success); + + if(!success) + { + char infoLog[512]; + glGetProgramInfoLog(prog_id, 512, NULL, infoLog); + std::cout << "Shader Link Error: " << infoLog << std::endl; + window::close(); + return 0; + } + + glUseProgram(prog_id); + glDeleteShader(vsh_id); + glDeleteShader(fsh_id); + + return prog_id; +} + diff --git a/src/graphics/shader.hpp b/src/graphics/shader.hpp new file mode 100644 index 0000000..63999c6 --- /dev/null +++ b/src/graphics/shader.hpp @@ -0,0 +1,10 @@ + +#pragma once + +namespace sim::graphics::shader +{ + +unsigned int init_program(); + +}; + diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp new file mode 100644 index 0000000..68ce88b --- /dev/null +++ b/src/graphics/window.cpp @@ -0,0 +1,89 @@ + +#include +#include + +#include + +#include "arrays.hpp" +#include "keyboard.hpp" +#include "resize.hpp" +#include "window.hpp" +#include "shader.hpp" +#include "font.hpp" + +using namespace sim::graphics; + +static GLFWwindow* win; +static bool win_should_close = false; + +void GLAPIENTRY cb_debug_message(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) +{ + std::cout << "GL CALLBACK: " << message << "\n"; +} + +void window::create() +{ + glfwInit(); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true); + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); + + win = glfwCreateWindow(800, 600, "FastNuclearSim", nullptr, nullptr); + + glfwMakeContextCurrent(win); + + GLenum err = glewInit(); + + if(err != GLEW_OK) + { + std::cout << "GLEW Init Failed: " << glewGetErrorString(err) << "\n"; + close(); + return; + } + + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(cb_debug_message, nullptr); + + keyboard::init(); + resize::init(); + font::init(); + + shader::init_program(); + arrays::init(); + + glViewport(0, 0, 800, 600); +} + +void window::loop() +{ + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + glfwSwapBuffers(win); + glfwPollEvents(); +} + +bool window::should_close() +{ + return win_should_close || glfwWindowShouldClose(win); +} + +void window::close() +{ + win_should_close = true; +} + +void window::destroy() +{ + glfwTerminate(); +} + +GLFWwindow* window::get_window() +{ + return win; +} + diff --git a/src/graphics/window.hpp b/src/graphics/window.hpp new file mode 100644 index 0000000..3b031fd --- /dev/null +++ b/src/graphics/window.hpp @@ -0,0 +1,18 @@ + +#pragma once + +#include + +namespace sim::graphics::window +{ + +void create(); +bool should_close(); +void loop(); +void destroy(); +void close(); + +GLFWwindow* get_window(); + +} + diff --git a/src/main.cpp b/src/main.cpp index 498291c..3cf4dcf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,216 +1,17 @@ -#include "reactor/builder.hpp" -#include "reactor/control/control_rod.hpp" -#include "reactor/fuel/fuel_rod.hpp" -#include "reactor/coolant/pipe.hpp" -#include "reactor/coolant/heater.hpp" -#include "reactor/coolant/vessel.hpp" -#include "coolant/fluid_t.hpp" -#include "coolant/valve.hpp" -#include "coolant/pump.hpp" -#include "display.hpp" +#include "graphics/window.hpp" -#include -#include -#include -#include -#include - -const bool do_graphics = true; -const bool do_clock = true; - -unsigned long get_now() -{ - struct timeval tv; - gettimeofday(&tv, nullptr); - return (unsigned long)tv.tv_sec * 1000000 + tv.tv_usec; -} +using namespace sim; int main() { - std::random_device rd; - std::mt19937 rand(rd()); - - if(do_graphics) + graphics::window::create(); + + while(!graphics::window::should_close()) { - initscr(); - cbreak(); - noecho(); - keypad(stdscr, TRUE); - nodelay(stdscr, TRUE); - curs_set(0); - } - - sim::reactor::coolant::vessel vessel(200, 400, sim::coolant::WATER); - sim::reactor::reactor<5, 5> reactor = sim::reactor::builder<5, 5>( - sim::reactor::fuel::fuel_rod(2000, 4000), - sim::reactor::control::control_rod(vessel, 10000, 1), - sim::reactor::coolant::pipe(vessel), { - "#C#C#", - "CFCFC", - "#C#C#", - "CFCFC", - "#C#C#" - }); - - sim::coolant::valve valve(vessel, 1, 500); - sim::coolant::pump pump(vessel, 1e4, 15); - - double secs = 0; - long clock = get_now(); - double speed = 1; - int framerate = 100; - int steps_extra = 1; - - for(;;) - { - std::stringstream ss; - ss << "Reactor Core\n\n"; - - { - long mins = secs / 60; - long hours = mins / 60; - long days = hours / 24; - long years = days / 365; - double s = fmod(secs, 60); - - mins %= 60; - hours %= 24; - days %= 365; - - ss << "Time:\n"; - - if(years > 0) goto years; - if(days > 0) goto days; - if(hours > 0) goto hours; - if(mins > 0) goto mins; - goto secs; - -years: ss << years << "y "; -days: ss << days << "d "; -hours: ss << hours << "h "; -mins: ss << mins << "m "; -secs: ss << s << "s\n"; - - ss << "Speed: " << speed << "x\n\n"; - } - - for(int i = 0; i < steps_extra; i++) - { - double dt = speed / framerate / steps_extra; - reactor.update(rand, dt); - pump.update(dt); - valve.update(dt); - vessel.update(); - secs += dt; - } - - ss << "Vessel\n" << vessel << "\n"; - ss << "Steam Valve\n" << valve << "\n"; - ss << "Coolant Pump\n" << pump << "\n"; - - if(do_graphics) - { - erase(); - display::draw_text(1, 0, ss.str().c_str()); - } - - const int X = 1, Y = 30; - const int W = 36, H = 11; - - for(int x = 0; x < reactor.width; x++) - for(int y = 0; y < reactor.height; y++) - { - int id = y * reactor.width + x; - sim::reactor::rod* r = reactor.rods[id]; - - if(!r->should_display()) - { - continue; - } - - std::stringstream ss; - ss << *r; - - int px = X + (H - 1) * y; - int py = Y + (W - 1) * x; - - if(do_graphics) - { - display::draw_text(px + 1, py + 2, ss.str().c_str()); - display::draw_box(px, py, H, W); - } - - if(do_graphics && r->should_select() && id == reactor.cursor) - { - display::draw_text(px + 1, py + W - 5, "[ ]"); - } - - if(do_graphics && r->is_selected()) - { - display::draw_text(px + 1, py + W - 4, "#"); - } - } - - int c = 0; - - if(do_graphics) - { - refresh(); - c = getch(); - } - - switch(c) - { - case KEY_LEFT: - reactor.move_cursor(-1); - break; - case KEY_RIGHT: - reactor.move_cursor(1); - break; - case KEY_UP: - reactor.update_selected(1); - break; - case KEY_DOWN: - reactor.update_selected(-1); - break; - case ' ': - reactor.toggle_selected(); - break; - case 't': - speed *= 10; - break; - case 'g': - speed /= 10; - break; - case 'r': - valve.open(0.001); - break; - case 'f': - valve.open(-0.001); - break; - case 'e': - pump.change_speed(0.01); - break; - case 'd': - pump.change_speed(-0.01); - break; - } - - if(do_clock) - { - long now = get_now(); - - while(clock + 1e6 / framerate > now) - { - usleep(clock + 1e6 / framerate - now); - now = get_now(); - } - - clock += 1e6 / framerate; - } + graphics::window::loop(); } - return 0; + graphics::window::destroy(); } diff --git a/src/main.cpp.old b/src/main.cpp.old new file mode 100644 index 0000000..498291c --- /dev/null +++ b/src/main.cpp.old @@ -0,0 +1,216 @@ + +#include "reactor/builder.hpp" +#include "reactor/control/control_rod.hpp" +#include "reactor/fuel/fuel_rod.hpp" +#include "reactor/coolant/pipe.hpp" +#include "reactor/coolant/heater.hpp" +#include "reactor/coolant/vessel.hpp" +#include "coolant/fluid_t.hpp" +#include "coolant/valve.hpp" +#include "coolant/pump.hpp" +#include "display.hpp" + +#include +#include +#include +#include +#include + +const bool do_graphics = true; +const bool do_clock = true; + +unsigned long get_now() +{ + struct timeval tv; + gettimeofday(&tv, nullptr); + return (unsigned long)tv.tv_sec * 1000000 + tv.tv_usec; +} + +int main() +{ + std::random_device rd; + std::mt19937 rand(rd()); + + if(do_graphics) + { + initscr(); + cbreak(); + noecho(); + keypad(stdscr, TRUE); + nodelay(stdscr, TRUE); + curs_set(0); + } + + sim::reactor::coolant::vessel vessel(200, 400, sim::coolant::WATER); + sim::reactor::reactor<5, 5> reactor = sim::reactor::builder<5, 5>( + sim::reactor::fuel::fuel_rod(2000, 4000), + sim::reactor::control::control_rod(vessel, 10000, 1), + sim::reactor::coolant::pipe(vessel), { + "#C#C#", + "CFCFC", + "#C#C#", + "CFCFC", + "#C#C#" + }); + + sim::coolant::valve valve(vessel, 1, 500); + sim::coolant::pump pump(vessel, 1e4, 15); + + double secs = 0; + long clock = get_now(); + double speed = 1; + int framerate = 100; + int steps_extra = 1; + + for(;;) + { + std::stringstream ss; + ss << "Reactor Core\n\n"; + + { + long mins = secs / 60; + long hours = mins / 60; + long days = hours / 24; + long years = days / 365; + double s = fmod(secs, 60); + + mins %= 60; + hours %= 24; + days %= 365; + + ss << "Time:\n"; + + if(years > 0) goto years; + if(days > 0) goto days; + if(hours > 0) goto hours; + if(mins > 0) goto mins; + goto secs; + +years: ss << years << "y "; +days: ss << days << "d "; +hours: ss << hours << "h "; +mins: ss << mins << "m "; +secs: ss << s << "s\n"; + + ss << "Speed: " << speed << "x\n\n"; + } + + for(int i = 0; i < steps_extra; i++) + { + double dt = speed / framerate / steps_extra; + reactor.update(rand, dt); + pump.update(dt); + valve.update(dt); + vessel.update(); + secs += dt; + } + + ss << "Vessel\n" << vessel << "\n"; + ss << "Steam Valve\n" << valve << "\n"; + ss << "Coolant Pump\n" << pump << "\n"; + + if(do_graphics) + { + erase(); + display::draw_text(1, 0, ss.str().c_str()); + } + + const int X = 1, Y = 30; + const int W = 36, H = 11; + + for(int x = 0; x < reactor.width; x++) + for(int y = 0; y < reactor.height; y++) + { + int id = y * reactor.width + x; + sim::reactor::rod* r = reactor.rods[id]; + + if(!r->should_display()) + { + continue; + } + + std::stringstream ss; + ss << *r; + + int px = X + (H - 1) * y; + int py = Y + (W - 1) * x; + + if(do_graphics) + { + display::draw_text(px + 1, py + 2, ss.str().c_str()); + display::draw_box(px, py, H, W); + } + + if(do_graphics && r->should_select() && id == reactor.cursor) + { + display::draw_text(px + 1, py + W - 5, "[ ]"); + } + + if(do_graphics && r->is_selected()) + { + display::draw_text(px + 1, py + W - 4, "#"); + } + } + + int c = 0; + + if(do_graphics) + { + refresh(); + c = getch(); + } + + switch(c) + { + case KEY_LEFT: + reactor.move_cursor(-1); + break; + case KEY_RIGHT: + reactor.move_cursor(1); + break; + case KEY_UP: + reactor.update_selected(1); + break; + case KEY_DOWN: + reactor.update_selected(-1); + break; + case ' ': + reactor.toggle_selected(); + break; + case 't': + speed *= 10; + break; + case 'g': + speed /= 10; + break; + case 'r': + valve.open(0.001); + break; + case 'f': + valve.open(-0.001); + break; + case 'e': + pump.change_speed(0.01); + break; + case 'd': + pump.change_speed(-0.01); + break; + } + + if(do_clock) + { + long now = get_now(); + + while(clock + 1e6 / framerate > now) + { + usleep(clock + 1e6 / framerate - now); + now = get_now(); + } + + clock += 1e6 / framerate; + } + } + + return 0; +} +