From 583955d97a86e1312444789cb3e88bc29529c8d9 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sun, 21 Jan 2024 01:36:21 +1100 Subject: [PATCH 01/43] 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; +} + From 4f82475251d47b6416d865f94299ef25ca701da9 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sun, 21 Jan 2024 12:58:37 +1100 Subject: [PATCH 02/43] adding models --- src/graphics/arrays.cpp | 15 +++++++++++++++ src/graphics/shader.cpp | 12 +++++++++++- src/graphics/shader.hpp | 3 +++ src/graphics/window.cpp | 21 +++++++++++++++++++-- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/graphics/arrays.cpp b/src/graphics/arrays.cpp index 62447f9..41aae77 100644 --- a/src/graphics/arrays.cpp +++ b/src/graphics/arrays.cpp @@ -2,6 +2,8 @@ #include #include +#include + #include "arrays.hpp" #include "font.hpp" @@ -14,11 +16,24 @@ static void* ptr_diff(void* a, void* b) return (void*)((size_t)a - (size_t)b); } +void processNode(aiNode *node, const aiScene *scene) +{ + +} + +Mesh processMesh(aiMesh *mesh, const aiScene *scene) +{ + +} + unsigned int arrays::init() { vertex v; unsigned long handle = font::chars['*'].handle; + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile("monkey.obj", aiProcess_Triangulate | aiProcess_FlipUVs); + 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}}, diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp index 66a0e02..7784dc1 100644 --- a/src/graphics/shader.cpp +++ b/src/graphics/shader.cpp @@ -38,15 +38,22 @@ in vec2 texPos; out vec4 FragColour; +uniform bool do_tex; +uniform mat4 tex_mat; + void main() { - FragColour = vec4(1) * texture2D(tex, texPos); + vec4 texdata = do_tex ? texture2D(tex, texPos) : vec4(1); + FragColour = tex_mat * texdata; } )"; static unsigned int prog_id; +int shader::gl_tex_mat; +int shader::gl_do_tex; + static int load_shader(const char** src, int type) { int id = glCreateShader(type); @@ -77,6 +84,9 @@ unsigned int shader::init_program() window::close(); return 0; } + + gl_tex_mat = glGetUniformLocation(prog_id, "tex_mat"); + gl_do_tex = glGetUniformLocation(prog_id, "do_tex"); glUseProgram(prog_id); glDeleteShader(vsh_id); diff --git a/src/graphics/shader.hpp b/src/graphics/shader.hpp index 63999c6..ad15fe8 100644 --- a/src/graphics/shader.hpp +++ b/src/graphics/shader.hpp @@ -4,6 +4,9 @@ namespace sim::graphics::shader { +extern int gl_tex_mat; +extern int gl_do_tex; + unsigned int init_program(); }; diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 68ce88b..4032059 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -2,6 +2,8 @@ #include #include +#include + #include #include "arrays.hpp" @@ -27,7 +29,7 @@ void window::create() 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_FORWARD_COMPAT, true); glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); win = glfwCreateWindow(800, 600, "FastNuclearSim", nullptr, nullptr); @@ -45,6 +47,10 @@ void window::create() glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDebugMessageCallback(cb_debug_message, nullptr); keyboard::init(); @@ -59,8 +65,19 @@ void window::create() void window::loop() { - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClearColor(0.2f, 0.3f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glm::mat4 m = { + 1, 1, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &m[0][0]); + glUniform1i(shader::gl_do_tex, 1); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glfwSwapBuffers(win); From 3217b3a1a59406fff91cd74be632df1d577a5dd1 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sun, 21 Jan 2024 22:33:55 +1100 Subject: [PATCH 03/43] things are rendering --- CMakeLists.txt | 2 +- src/graphics/arrays.cpp | 42 +------------- src/graphics/arrays.hpp | 11 ++-- src/graphics/model.cpp | 122 ++++++++++++++++++++++++++++++++++++++++ src/graphics/model.hpp | 25 ++++++++ src/graphics/resize.cpp | 10 ++++ src/graphics/resize.hpp | 4 ++ src/graphics/shader.cpp | 15 ++++- src/graphics/shader.hpp | 2 + src/graphics/window.cpp | 30 +++++++--- 10 files changed, 209 insertions(+), 54 deletions(-) create mode 100644 src/graphics/model.cpp create mode 100644 src/graphics/model.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 074a5de..2649105 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,5 +8,5 @@ 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 GLEW glfw GL freetype) +target_link_libraries(FastNuclearSim PUBLIC stdc++ m GLEW glfw GL freetype assimp) diff --git a/src/graphics/arrays.cpp b/src/graphics/arrays.cpp index 41aae77..ced4c60 100644 --- a/src/graphics/arrays.cpp +++ b/src/graphics/arrays.cpp @@ -4,6 +4,7 @@ #include +#include "shader.hpp" #include "arrays.hpp" #include "font.hpp" @@ -16,46 +17,9 @@ static void* ptr_diff(void* a, void* b) return (void*)((size_t)a - (size_t)b); } -void processNode(aiNode *node, const aiScene *scene) -{ - -} - -Mesh processMesh(aiMesh *mesh, const aiScene *scene) -{ - -} - -unsigned int arrays::init() +void arrays::vertex_attrib_pointers() { vertex v; - unsigned long handle = font::chars['*'].handle; - - Assimp::Importer importer; - const aiScene *scene = importer.ReadFile("monkey.obj", aiProcess_Triangulate | aiProcess_FlipUVs); - - 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); @@ -65,7 +29,5 @@ unsigned int arrays::init() 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 index 14dba03..2f8bc26 100644 --- a/src/graphics/arrays.hpp +++ b/src/graphics/arrays.hpp @@ -9,12 +9,15 @@ namespace sim::graphics::arrays struct vertex { - unsigned long texid; - glm::vec2 texpos; - glm::vec3 pos; + unsigned long texid = 0; + glm::vec2 texpos = {0, 0}; + glm::vec3 pos = {0, 0, 0}; + + vertex() { } + vertex(unsigned long texid, glm::vec2 texpos, glm::vec3 pos) : texid(texid), texpos(texpos), pos(pos) { } }; -unsigned int init(); +void vertex_attrib_pointers(); }; diff --git a/src/graphics/model.cpp b/src/graphics/model.cpp new file mode 100644 index 0000000..c8349dd --- /dev/null +++ b/src/graphics/model.cpp @@ -0,0 +1,122 @@ + +#include +#include + +#include +#include +#include + +#include + +#include "model.hpp" +#include "arrays.hpp" + +using namespace sim::graphics; + +model::model() +{ + +} + +model::~model() +{ + if(vbo) glDeleteBuffers(1, &vbo); + if(ebo) glDeleteBuffers(1, &ebo); + if(vao) glDeleteVertexArrays(1, &vao); +} + +void model::alloc() +{ + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + glGenBuffers(1, &ebo); + + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + + arrays::vertex_attrib_pointers(); +} + +void model::load_vbo(const arrays::vertex* data, size_t size, int mode) +{ + glBufferData(GL_ARRAY_BUFFER, size * sizeof(data[0]), data, mode); +} + +void model::load_ebo(const unsigned int* data, size_t size, int mode) +{ + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size * sizeof(data[0]), data, mode); + this->size = size; +} + +struct proc_state +{ + size_t at = 0; + std::vector vertices; + std::vector indices; +}; + +static void proc_mesh(proc_state& state, aiMesh* mesh, const aiScene* scene) +{ + for(unsigned int i = 0; i < mesh->mNumVertices; i++) + { + arrays::vertex vertex; + + vertex.pos = {mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z}; + + state.vertices.push_back(vertex); + } + + unsigned int at = state.at; + + for(unsigned int i = 0; i < mesh->mNumFaces; i++) + { + aiFace face = mesh->mFaces[i]; + unsigned int j; + + for(j = 0; j < face.mNumIndices; j++) + { + state.indices.push_back(face.mIndices[j] + state.at); + } + + at += j; + } + + state.at = at; +} + +static void proc_node(proc_state& state, aiNode* node, const aiScene* scene) +{ + for(size_t i = 0; i < node->mNumMeshes; i++) + { + aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; + proc_mesh(state, mesh, scene); + } + + for(size_t i = 0; i < node->mNumChildren; i++) + { + proc_node(state, node->mChildren[i], scene); + } +} + +void model::load(const char* path) +{ + proc_state state; + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs); + proc_node(state, scene->mRootNode, scene); + + load_vbo(&state.vertices[0], state.vertices.size(), GL_STATIC_DRAW); + load_ebo(&state.indices[0], state.indices.size(), GL_STATIC_DRAW); +} + +void model::bind() +{ + glBindVertexArray(vao); +} + +void model::render() +{ + glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0); +} + diff --git a/src/graphics/model.hpp b/src/graphics/model.hpp new file mode 100644 index 0000000..c181f3f --- /dev/null +++ b/src/graphics/model.hpp @@ -0,0 +1,25 @@ + +#pragma once + +#include "arrays.hpp" + +namespace sim::graphics +{ + +struct model +{ + unsigned int vao = 0, vbo = 0, ebo = 0, size = 0; + + model(); + ~model(); + + void bind(); + void alloc(); + void load(const char* path); + void load_vbo(const arrays::vertex* data, size_t size, int mode); + void load_ebo(const unsigned int* data, size_t size, int mode); + void render(); +}; + +}; + diff --git a/src/graphics/resize.cpp b/src/graphics/resize.cpp index 20dda38..238292a 100644 --- a/src/graphics/resize.cpp +++ b/src/graphics/resize.cpp @@ -17,6 +17,16 @@ static int win_restore_h; static int win_restore_x; static int win_restore_y; +glm::vec2 resize::get_size() +{ + return {win_w, win_h}; +} + +double resize::get_aspect() +{ + return win_w / win_h; +} + void resize::toggle_fullscreen() { GLFWwindow* win = window::get_window(); diff --git a/src/graphics/resize.hpp b/src/graphics/resize.hpp index 50e0eb1..ca060e8 100644 --- a/src/graphics/resize.hpp +++ b/src/graphics/resize.hpp @@ -1,11 +1,15 @@ #pragma once +#include + namespace sim::graphics::resize { void init(); void toggle_fullscreen(); +glm::vec2 get_size(); +double get_aspect(); }; diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp index 7784dc1..e6a6fce 100644 --- a/src/graphics/shader.cpp +++ b/src/graphics/shader.cpp @@ -17,14 +17,20 @@ layout (location = 0) in sampler2D aTex; layout (location = 1) in vec2 aTexPos; layout (location = 2) in vec3 aPos; +uniform mat4 model; +uniform mat4 projection; + out flat sampler2D tex; out vec2 texPos; +out float zVal; void main() { - gl_Position = vec4(aPos, 1.0); + vec4 pos = model * vec4(aPos, 1.0); + gl_Position = projection * pos; texPos = aTexPos; tex = aTex; + zVal = 8.0f / (pos.z * pos.z); } )"; @@ -35,6 +41,7 @@ static const char* FRAGMENT_SHADER = R"( in flat sampler2D tex; in vec2 texPos; +in float zVal; out vec4 FragColour; @@ -44,7 +51,7 @@ uniform mat4 tex_mat; void main() { vec4 texdata = do_tex ? texture2D(tex, texPos) : vec4(1); - FragColour = tex_mat * texdata; + FragColour = tex_mat * texdata * vec4(zVal, zVal, zVal, 1); } )"; @@ -53,6 +60,8 @@ static unsigned int prog_id; int shader::gl_tex_mat; int shader::gl_do_tex; +int shader::gl_model; +int shader::gl_projection; static int load_shader(const char** src, int type) { @@ -87,6 +96,8 @@ unsigned int shader::init_program() gl_tex_mat = glGetUniformLocation(prog_id, "tex_mat"); gl_do_tex = glGetUniformLocation(prog_id, "do_tex"); + gl_model = glGetUniformLocation(prog_id, "model"); + gl_projection = glGetUniformLocation(prog_id, "projection"); glUseProgram(prog_id); glDeleteShader(vsh_id); diff --git a/src/graphics/shader.hpp b/src/graphics/shader.hpp index ad15fe8..a68e99d 100644 --- a/src/graphics/shader.hpp +++ b/src/graphics/shader.hpp @@ -6,6 +6,8 @@ namespace sim::graphics::shader extern int gl_tex_mat; extern int gl_do_tex; +extern int gl_model; +extern int gl_projection; unsigned int init_program(); diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 4032059..71c31e4 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -3,9 +3,12 @@ #include #include +#include // glm::translate, glm::rotate, glm::scale +#include // glm::perspective #include +#include "model.hpp" #include "arrays.hpp" #include "keyboard.hpp" #include "resize.hpp" @@ -17,6 +20,7 @@ using namespace sim::graphics; static GLFWwindow* win; static bool win_should_close = false; +static model Model; void GLAPIENTRY cb_debug_message(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { @@ -56,29 +60,41 @@ void window::create() keyboard::init(); resize::init(); font::init(); - shader::init_program(); - arrays::init(); + + Model.alloc(); + Model.load("monkey.obj"); glViewport(0, 0, 800, 600); } void window::loop() { - glClearColor(0.2f, 0.3f, 0.5f, 1.0f); + glClearColor(0, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glm::mat4 m = { + glm::mat4 mat_colour = { 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &m[0][0]); - glUniform1i(shader::gl_do_tex, 1); + glm::mat4 mat_model = glm::mat4(1.0f); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + mat_model = glm::translate(mat_model, glm::vec3(0.0f, 0.0f, -5.0f)); +// mat_model = glm::scale(mat_model, glm::vec3(1.0f) * 0.05f); + mat_model = glm::rotate(mat_model, float(M_PI * 0.125), glm::vec3(1, 1, 1)); + + glm::mat4 mat_projection = glm::perspective(float(M_PI * 0.25), (float)resize::get_aspect(), 0.1f, 100.f); + + glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &mat_colour[0][0]); + glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); + glUniformMatrix4fv(shader::gl_model, 1, false, &mat_model[0][0]); + glUniform1i(shader::gl_do_tex, 0); + + Model.bind(); + Model.render(); glfwSwapBuffers(win); glfwPollEvents(); From ff852046c11d7f56bbe43790ded7ebfa77de2acc Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Mon, 22 Jan 2024 00:20:54 +1100 Subject: [PATCH 04/43] added movement --- src/graphics/camera.cpp | 93 +++++++++++++++++++++++++++++++++++++++ src/graphics/camera.hpp | 15 +++++++ src/graphics/keyboard.cpp | 30 +++++++++++++ src/graphics/keyboard.hpp | 1 + src/graphics/mouse.cpp | 34 ++++++++++++++ src/graphics/mouse.hpp | 11 +++++ src/graphics/window.cpp | 12 ++++- 7 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 src/graphics/camera.cpp create mode 100644 src/graphics/camera.hpp create mode 100644 src/graphics/mouse.cpp create mode 100644 src/graphics/mouse.hpp diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp new file mode 100644 index 0000000..473e200 --- /dev/null +++ b/src/graphics/camera.cpp @@ -0,0 +1,93 @@ + +#include +#include + +#include "camera.hpp" +#include "keyboard.hpp" + +#include +#include +#include + +using namespace sim::graphics; + +static double yaw = 0, pitch = 0; +static double x = 0, y = 0, z = 0; + +void camera::rotate(double y, double p) +{ + yaw += y * 0.1; + pitch += p * 0.1; + + if(pitch < -90) pitch = -90; + if(pitch > 90) pitch = 90; +} + +void camera::move(double xoff, double yoff, double zoff) +{ + x += xoff; + y += yoff; + z += zoff; +} + +void camera::update() +{ + double xoff = 0, yoff = 0, zoff = 0; + + glm::vec<3, double> off(0, 0, 0); + + if(keyboard::is_pressed(GLFW_KEY_SPACE)) + { + off.y -= 1; + } + + if(keyboard::is_pressed(GLFW_KEY_LEFT_SHIFT)) + { + off.y += 1; + } + + if(keyboard::is_pressed(GLFW_KEY_W)) + { + off.z += 1; + } + + if(keyboard::is_pressed(GLFW_KEY_S)) + { + off.z -= 1; + } + + if(keyboard::is_pressed(GLFW_KEY_A)) + { + off.x += 1; + } + + if(keyboard::is_pressed(GLFW_KEY_D)) + { + off.x -= 1; + } + + double angle = -glm::radians(yaw); + + glm::mat<2, 2, double> mat = { + std::cos(angle), std::sin(angle), + -std::sin(angle), std::cos(angle) + }; + + glm::vec<2, double> rotated = glm::vec<2, double>(off.x, off.z) * mat; + + y += off.y * 0.1; + x += rotated.x * 0.1; + z += rotated.y * 0.1; +} + +glm::mat4 camera::get() +{ + glm::mat4 mat(1); + + mat = glm::rotate(mat, (float)glm::radians(pitch), glm::vec3(1, 0, 0)); + mat = glm::rotate(mat, (float)glm::radians(yaw), glm::vec3(0, 1, 0)); + mat = glm::translate(mat, glm::vec3(x, y, z)); + + return mat; +} + diff --git a/src/graphics/camera.hpp b/src/graphics/camera.hpp new file mode 100644 index 0000000..1f81ccb --- /dev/null +++ b/src/graphics/camera.hpp @@ -0,0 +1,15 @@ + +#pragma once + +#include + +namespace sim::graphics::camera +{ + +glm::mat4 get(); +void rotate(double pitch, double yaw); +void move(double x, double y, double z); +void update(); + +}; + diff --git a/src/graphics/keyboard.cpp b/src/graphics/keyboard.cpp index efadd6c..aa77087 100644 --- a/src/graphics/keyboard.cpp +++ b/src/graphics/keyboard.cpp @@ -2,18 +2,48 @@ #include #include +#include + #include "keyboard.hpp" #include "window.hpp" #include "resize.hpp" +#include "camera.hpp" using namespace sim::graphics; +static std::unordered_map pressed; + 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(); } + + if(action == GLFW_PRESS) + { + pressed[key] = true; + } + + if(action == GLFW_RELEASE) + { + pressed[key] = false; + } +} + +bool keyboard::is_pressed(int key) +{ + auto it = pressed.find(key); + + if(it == pressed.end()) + { + return false; + } + + else + { + return it->second; + } } void keyboard::init() diff --git a/src/graphics/keyboard.hpp b/src/graphics/keyboard.hpp index 6ad89db..9762383 100644 --- a/src/graphics/keyboard.hpp +++ b/src/graphics/keyboard.hpp @@ -5,6 +5,7 @@ namespace sim::graphics::keyboard { void init(); +bool is_pressed(int key); }; diff --git a/src/graphics/mouse.cpp b/src/graphics/mouse.cpp new file mode 100644 index 0000000..05d10fc --- /dev/null +++ b/src/graphics/mouse.cpp @@ -0,0 +1,34 @@ + +#include +#include + +#include "mouse.hpp" +#include "window.hpp" +#include "camera.hpp" + +using namespace sim::graphics; + +static double xpos = 0, ypos = 0; + +static void cb_cursor_pos(GLFWwindow* win, double x, double y) +{ + camera::rotate(x - xpos, y - ypos); + + xpos = x; + ypos = y; +} + +void mouse::get(double& x, double& y) +{ + x = xpos; + y = ypos; +} + +void mouse::init() +{ + GLFWwindow* win = window::get_window(); + glfwSetCursorPosCallback(win, cb_cursor_pos); + glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + glfwSetCursorPos(win, 0, 0); +} + diff --git a/src/graphics/mouse.hpp b/src/graphics/mouse.hpp new file mode 100644 index 0000000..20125d9 --- /dev/null +++ b/src/graphics/mouse.hpp @@ -0,0 +1,11 @@ + +#pragma once + +namespace sim::graphics::mouse +{ + +void init(); +void get(double& x, double& y); + +}; + diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 71c31e4..fe0447d 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -11,6 +11,8 @@ #include "model.hpp" #include "arrays.hpp" #include "keyboard.hpp" +#include "mouse.hpp" +#include "camera.hpp" #include "resize.hpp" #include "window.hpp" #include "shader.hpp" @@ -58,8 +60,10 @@ void window::create() glDebugMessageCallback(cb_debug_message, nullptr); keyboard::init(); + mouse::init(); resize::init(); font::init(); + shader::init_program(); Model.alloc(); @@ -80,10 +84,14 @@ void window::loop() 0, 0, 0, 0 }; - glm::mat4 mat_model = glm::mat4(1.0f); + camera::update(); + + glm::mat4 mat_model = camera::get(); + + double mouse_x, mouse_y; + mouse::get(mouse_x, mouse_y); mat_model = glm::translate(mat_model, glm::vec3(0.0f, 0.0f, -5.0f)); -// mat_model = glm::scale(mat_model, glm::vec3(1.0f) * 0.05f); mat_model = glm::rotate(mat_model, float(M_PI * 0.125), glm::vec3(1, 1, 1)); glm::mat4 mat_projection = glm::perspective(float(M_PI * 0.25), (float)resize::get_aspect(), 0.1f, 100.f); From fc8be2827ded2960159255a26968cebb0e596102 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Mon, 22 Jan 2024 23:53:10 +1100 Subject: [PATCH 05/43] added normals --- src/graphics/arrays.cpp | 3 ++ src/graphics/arrays.hpp | 1 + src/graphics/camera.cpp | 29 +++---------- src/graphics/camera.hpp | 3 +- src/graphics/mesh.cpp | 62 ++++++++++++++++++++++++++++ src/graphics/mesh.hpp | 25 +++++++++++ src/graphics/model.cpp | 91 +++++++++++------------------------------ src/graphics/model.hpp | 15 +++---- src/graphics/shader.cpp | 9 +++- src/graphics/window.cpp | 7 ++-- 10 files changed, 140 insertions(+), 105 deletions(-) create mode 100644 src/graphics/mesh.cpp create mode 100644 src/graphics/mesh.hpp diff --git a/src/graphics/arrays.cpp b/src/graphics/arrays.cpp index ced4c60..9d1bfbb 100644 --- a/src/graphics/arrays.cpp +++ b/src/graphics/arrays.cpp @@ -29,5 +29,8 @@ void arrays::vertex_attrib_pointers() glVertexAttribPointer(2, 3, GL_FLOAT, false, sizeof(v), ptr_diff(&v.pos, &v)); glEnableVertexAttribArray(2); + + glVertexAttribPointer(3, 3, GL_FLOAT, false, sizeof(v), ptr_diff(&v.normal, &v)); + glEnableVertexAttribArray(3); } diff --git a/src/graphics/arrays.hpp b/src/graphics/arrays.hpp index 2f8bc26..b158d3c 100644 --- a/src/graphics/arrays.hpp +++ b/src/graphics/arrays.hpp @@ -12,6 +12,7 @@ struct vertex unsigned long texid = 0; glm::vec2 texpos = {0, 0}; glm::vec3 pos = {0, 0, 0}; + glm::vec3 normal = {0, 0, 0}; vertex() { } vertex(unsigned long texid, glm::vec2 texpos, glm::vec3 pos) : texid(texid), texpos(texpos), pos(pos) { } diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index 473e200..9a87165 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -16,8 +16,8 @@ static double x = 0, y = 0, z = 0; void camera::rotate(double y, double p) { - yaw += y * 0.1; - pitch += p * 0.1; + yaw += y * 0.05; + pitch += p * 0.05; if(pitch < -90) pitch = -90; if(pitch > 90) pitch = 90; @@ -37,34 +37,17 @@ void camera::update() glm::vec<3, double> off(0, 0, 0); if(keyboard::is_pressed(GLFW_KEY_SPACE)) - { off.y -= 1; - } - if(keyboard::is_pressed(GLFW_KEY_LEFT_SHIFT)) - { off.y += 1; - } - if(keyboard::is_pressed(GLFW_KEY_W)) - { off.z += 1; - } - if(keyboard::is_pressed(GLFW_KEY_S)) - { off.z -= 1; - } - if(keyboard::is_pressed(GLFW_KEY_A)) - { off.x += 1; - } - if(keyboard::is_pressed(GLFW_KEY_D)) - { off.x -= 1; - } double angle = -glm::radians(yaw); @@ -75,12 +58,12 @@ void camera::update() glm::vec<2, double> rotated = glm::vec<2, double>(off.x, off.z) * mat; - y += off.y * 0.1; - x += rotated.x * 0.1; - z += rotated.y * 0.1; + y += off.y * 0.05; + x += rotated.x * 0.05; + z += rotated.y * 0.05; } -glm::mat4 camera::get() +glm::mat4 camera::get_model_matrix() { glm::mat4 mat(1); diff --git a/src/graphics/camera.hpp b/src/graphics/camera.hpp index 1f81ccb..37ac263 100644 --- a/src/graphics/camera.hpp +++ b/src/graphics/camera.hpp @@ -2,11 +2,12 @@ #pragma once #include +#include namespace sim::graphics::camera { -glm::mat4 get(); +glm::mat4 get_model_matrix(); void rotate(double pitch, double yaw); void move(double x, double y, double z); void update(); diff --git a/src/graphics/mesh.cpp b/src/graphics/mesh.cpp new file mode 100644 index 0000000..f561e96 --- /dev/null +++ b/src/graphics/mesh.cpp @@ -0,0 +1,62 @@ + +#include +#include + +#include "mesh.hpp" +#include "arrays.hpp" +#include + +using namespace sim::graphics; + +mesh::mesh() +{ + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + glGenBuffers(1, &ebo); + + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + + arrays::vertex_attrib_pointers(); +} + +mesh::mesh(mesh&& o) +{ + vbo = o.vbo; + ebo = o.ebo; + vao = o.vao; + size = o.size; + o.vbo = 0; + o.ebo = 0; + o.vao = 0; +} + +mesh::~mesh() +{ + if(vbo) glDeleteBuffers(1, &vbo); + if(ebo) glDeleteBuffers(1, &ebo); + if(vao) glDeleteVertexArrays(1, &vao); +} + +void mesh::set_vertices(const arrays::vertex* data, size_t size, int mode) +{ + glBufferData(GL_ARRAY_BUFFER, size * sizeof(data[0]), data, mode); +} + +void mesh::set_indices(const unsigned int* data, size_t size, int mode) +{ + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size * sizeof(data[0]), data, mode); + this->size = size; +} + +void mesh::bind() const +{ + glBindVertexArray(vao); +} + +void mesh::render() const +{ + glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0); +} + diff --git a/src/graphics/mesh.hpp b/src/graphics/mesh.hpp new file mode 100644 index 0000000..b5f9b2a --- /dev/null +++ b/src/graphics/mesh.hpp @@ -0,0 +1,25 @@ + +#pragma once + +#include "arrays.hpp" + +namespace sim::graphics +{ + +struct mesh +{ + unsigned int vao = 0, vbo = 0, ebo = 0, size = 0; + + mesh(); + mesh(mesh&& o); + mesh(const mesh& o) = delete; + ~mesh(); + + void bind() const; + void set_vertices(const arrays::vertex* data, size_t size, int mode); + void set_indices(const unsigned int* data, size_t size, int mode); + void render() const; +}; + +}; + diff --git a/src/graphics/model.cpp b/src/graphics/model.cpp index c8349dd..6139305 100644 --- a/src/graphics/model.cpp +++ b/src/graphics/model.cpp @@ -8,115 +8,74 @@ #include +#include "mesh.hpp" #include "model.hpp" #include "arrays.hpp" using namespace sim::graphics; -model::model() +static void proc_mesh(std::vector& meshes, aiMesh* mesh, const aiScene* scene) { - -} - -model::~model() -{ - if(vbo) glDeleteBuffers(1, &vbo); - if(ebo) glDeleteBuffers(1, &ebo); - if(vao) glDeleteVertexArrays(1, &vao); -} - -void model::alloc() -{ - glGenVertexArrays(1, &vao); - glGenBuffers(1, &vbo); - glGenBuffers(1, &ebo); - - glBindVertexArray(vao); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); - - arrays::vertex_attrib_pointers(); -} - -void model::load_vbo(const arrays::vertex* data, size_t size, int mode) -{ - glBufferData(GL_ARRAY_BUFFER, size * sizeof(data[0]), data, mode); -} - -void model::load_ebo(const unsigned int* data, size_t size, int mode) -{ - glBufferData(GL_ELEMENT_ARRAY_BUFFER, size * sizeof(data[0]), data, mode); - this->size = size; -} - -struct proc_state -{ - size_t at = 0; std::vector vertices; std::vector indices; -}; - -static void proc_mesh(proc_state& state, aiMesh* mesh, const aiScene* scene) -{ + for(unsigned int i = 0; i < mesh->mNumVertices; i++) { arrays::vertex vertex; vertex.pos = {mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z}; - state.vertices.push_back(vertex); - } + if(mesh->HasNormals()) + { + vertex.normal = {mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z}; + } - unsigned int at = state.at; + vertices.push_back(vertex); + } for(unsigned int i = 0; i < mesh->mNumFaces; i++) { aiFace face = mesh->mFaces[i]; - unsigned int j; - for(j = 0; j < face.mNumIndices; j++) + for(unsigned int j = 0; j < face.mNumIndices; j++) { - state.indices.push_back(face.mIndices[j] + state.at); + indices.push_back(face.mIndices[j]); } - - at += j; } - state.at = at; + sim::graphics::mesh m; + m.set_vertices(&vertices[0], vertices.size(), GL_STATIC_DRAW); + m.set_indices(&indices[0], indices.size(), GL_STATIC_DRAW); + meshes.push_back(std::move(m)); } -static void proc_node(proc_state& state, aiNode* node, const aiScene* scene) +static void proc_node(std::vector& meshes, aiNode* node, const aiScene* scene) { for(size_t i = 0; i < node->mNumMeshes; i++) { aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; - proc_mesh(state, mesh, scene); + proc_mesh(meshes, mesh, scene); } for(size_t i = 0; i < node->mNumChildren; i++) { - proc_node(state, node->mChildren[i], scene); + proc_node(meshes, node->mChildren[i], scene); } } void model::load(const char* path) { - proc_state state; Assimp::Importer importer; const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs); - proc_node(state, scene->mRootNode, scene); - - load_vbo(&state.vertices[0], state.vertices.size(), GL_STATIC_DRAW); - load_ebo(&state.indices[0], state.indices.size(), GL_STATIC_DRAW); + proc_node(meshes, scene->mRootNode, scene); } -void model::bind() +void model::render() const { - glBindVertexArray(vao); -} - -void model::render() -{ - glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0); + for(const mesh& m : meshes) + { + m.bind(); + m.render(); + } } diff --git a/src/graphics/model.hpp b/src/graphics/model.hpp index c181f3f..8a15c52 100644 --- a/src/graphics/model.hpp +++ b/src/graphics/model.hpp @@ -1,24 +1,19 @@ #pragma once -#include "arrays.hpp" +#include "mesh.hpp" + +#include namespace sim::graphics { struct model { - unsigned int vao = 0, vbo = 0, ebo = 0, size = 0; + std::vector meshes; - model(); - ~model(); - - void bind(); - void alloc(); void load(const char* path); - void load_vbo(const arrays::vertex* data, size_t size, int mode); - void load_ebo(const unsigned int* data, size_t size, int mode); - void render(); + void render() const; }; }; diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp index e6a6fce..f0bba1f 100644 --- a/src/graphics/shader.cpp +++ b/src/graphics/shader.cpp @@ -16,6 +16,7 @@ static const char* VERTEX_SHADER = R"( layout (location = 0) in sampler2D aTex; layout (location = 1) in vec2 aTexPos; layout (location = 2) in vec3 aPos; +layout (location = 3) in vec3 aNormal; uniform mat4 model; uniform mat4 projection; @@ -27,10 +28,16 @@ out float zVal; void main() { vec4 pos = model * vec4(aPos, 1.0); + + mat3 model_norm = mat3(model); + vec3 normal = aNormal; + vec3 cNormal = vec3(0.f, 0.f, 1.f) * model_norm; + + zVal = dot(normal, cNormal);// / (length(aNormal) * length(cNormal)); + gl_Position = projection * pos; texPos = aTexPos; tex = aTex; - zVal = 8.0f / (pos.z * pos.z); } )"; diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index fe0447d..01d267c 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -66,8 +66,7 @@ void window::create() shader::init_program(); - Model.alloc(); - Model.load("monkey.obj"); + Model.load("teapot.obj"); glViewport(0, 0, 800, 600); } @@ -86,13 +85,14 @@ void window::loop() camera::update(); - glm::mat4 mat_model = camera::get(); + glm::mat4 mat_model = camera::get_model_matrix(); double mouse_x, mouse_y; mouse::get(mouse_x, mouse_y); mat_model = glm::translate(mat_model, glm::vec3(0.0f, 0.0f, -5.0f)); mat_model = glm::rotate(mat_model, float(M_PI * 0.125), glm::vec3(1, 1, 1)); + mat_model = glm::scale(mat_model, glm::vec3(1, 1, 1) * 0.2f); glm::mat4 mat_projection = glm::perspective(float(M_PI * 0.25), (float)resize::get_aspect(), 0.1f, 100.f); @@ -101,7 +101,6 @@ void window::loop() glUniformMatrix4fv(shader::gl_model, 1, false, &mat_model[0][0]); glUniform1i(shader::gl_do_tex, 0); - Model.bind(); Model.render(); glfwSwapBuffers(win); From 1d288a452b005360a0a50ba143f9b5aff2502f09 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Tue, 23 Jan 2024 17:46:33 +1100 Subject: [PATCH 06/43] added textures! --- src/graphics/arrays.cpp | 3 ++ src/graphics/arrays.hpp | 1 + src/graphics/camera.cpp | 72 +++++++++++++++++++++++++------------ src/graphics/font.cpp | 4 +-- src/graphics/model.cpp | 66 +++++++++++++++++++++++++++------- src/graphics/model.hpp | 3 +- src/graphics/shader.cpp | 20 +++++------ src/graphics/texture.cpp | 76 ++++++++++++++++++++++++++++++++++++++++ src/graphics/texture.hpp | 12 +++++++ src/graphics/window.cpp | 25 +++++++------ src/stb/stb_image.cpp | 4 +++ 11 files changed, 227 insertions(+), 59 deletions(-) create mode 100644 src/graphics/texture.cpp create mode 100644 src/graphics/texture.hpp create mode 100644 src/stb/stb_image.cpp diff --git a/src/graphics/arrays.cpp b/src/graphics/arrays.cpp index 9d1bfbb..8da8753 100644 --- a/src/graphics/arrays.cpp +++ b/src/graphics/arrays.cpp @@ -32,5 +32,8 @@ void arrays::vertex_attrib_pointers() glVertexAttribPointer(3, 3, GL_FLOAT, false, sizeof(v), ptr_diff(&v.normal, &v)); glEnableVertexAttribArray(3); + + glVertexAttribPointer(4, 1, GL_FLOAT, false, sizeof(v), ptr_diff(&v.do_tex, &v)); + glEnableVertexAttribArray(4); } diff --git a/src/graphics/arrays.hpp b/src/graphics/arrays.hpp index b158d3c..e9995d6 100644 --- a/src/graphics/arrays.hpp +++ b/src/graphics/arrays.hpp @@ -13,6 +13,7 @@ struct vertex glm::vec2 texpos = {0, 0}; glm::vec3 pos = {0, 0, 0}; glm::vec3 normal = {0, 0, 0}; + float do_tex = 0; vertex() { } vertex(unsigned long texid, glm::vec2 texpos, glm::vec3 pos) : texid(texid), texpos(texpos), pos(pos) { } diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index 9a87165..71c9aef 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -5,6 +5,7 @@ #include "camera.hpp" #include "keyboard.hpp" +#include #include #include #include @@ -12,55 +13,80 @@ using namespace sim::graphics; static double yaw = 0, pitch = 0; -static double x = 0, y = 0, z = 0; +static glm::vec<3, double> pos(56, 90, 8); +static glm::vec<3, double> velocity(0); void camera::rotate(double y, double p) { - yaw += y * 0.05; + yaw -= y * 0.05; pitch += p * 0.05; - if(pitch < -90) pitch = -90; - if(pitch > 90) pitch = 90; + if(pitch < 0) pitch = 0; + if(pitch > 180) pitch = 180; } void camera::move(double xoff, double yoff, double zoff) { - x += xoff; - y += yoff; - z += zoff; + pos.x += xoff; + pos.y += yoff; + pos.z += zoff; } void camera::update() { - double xoff = 0, yoff = 0, zoff = 0; - - glm::vec<3, double> off(0, 0, 0); + glm::vec<2, double> off(0, 0); + double m = 0.01; - if(keyboard::is_pressed(GLFW_KEY_SPACE)) - off.y -= 1; - if(keyboard::is_pressed(GLFW_KEY_LEFT_SHIFT)) - off.y += 1; if(keyboard::is_pressed(GLFW_KEY_W)) - off.z += 1; + off.y += 1; if(keyboard::is_pressed(GLFW_KEY_S)) - off.z -= 1; + off.y -= 1; if(keyboard::is_pressed(GLFW_KEY_A)) off.x += 1; if(keyboard::is_pressed(GLFW_KEY_D)) off.x -= 1; + if(keyboard::is_pressed(GLFW_KEY_LEFT_SHIFT)) + m *= 1.5; + if(off.x != 0 || off.y != 0) + off /= std::sqrt(off.x * off.x + off.y * off.y); - double angle = -glm::radians(yaw); + double angle = glm::radians(yaw); glm::mat<2, 2, double> mat = { std::cos(angle), std::sin(angle), -std::sin(angle), std::cos(angle) }; - glm::vec<2, double> rotated = glm::vec<2, double>(off.x, off.z) * mat; + glm::vec<2, double> rotated = glm::vec<2, double>(off.x, off.y) * mat; + bool on_ground = false; - y += off.y * 0.05; - x += rotated.x * 0.05; - z += rotated.y * 0.05; + velocity.z -= 0.00981; + + if(pos.z + velocity.z < 3.5) + { + on_ground = true; + + if(keyboard::is_pressed(GLFW_KEY_SPACE)) + { + velocity.z += 0.4; + } + + else + { + velocity.z = 0; + } + } + + else + { + m = 0; + } + + velocity.x += rotated.x * m; + velocity.y += rotated.y * m; + + pos += velocity; + velocity *= glm::vec<3, double>(on_ground ? 0.9 : 0.9999); } glm::mat4 camera::get_model_matrix() @@ -68,8 +94,8 @@ glm::mat4 camera::get_model_matrix() glm::mat4 mat(1); mat = glm::rotate(mat, (float)glm::radians(pitch), glm::vec3(1, 0, 0)); - mat = glm::rotate(mat, (float)glm::radians(yaw), glm::vec3(0, 1, 0)); - mat = glm::translate(mat, glm::vec3(x, y, z)); + mat = glm::rotate(mat, (float)glm::radians(yaw), glm::vec3(0, 0, 1)); + mat = glm::translate(mat, glm::vec3(pos.x, pos.y, pos.z)); return mat; } diff --git a/src/graphics/font.cpp b/src/graphics/font.cpp index 73d59f9..82a6d2f 100644 --- a/src/graphics/font.cpp +++ b/src/graphics/font.cpp @@ -33,8 +33,9 @@ void font::init() FT_Set_Pixel_Sizes(face, 0, 1024); GLuint texids[128]; - + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glCreateTextures(GL_TEXTURE_2D, 128, texids); for(int i = 0; i < 128; i++) { @@ -54,7 +55,6 @@ void font::init() 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); diff --git a/src/graphics/model.cpp b/src/graphics/model.cpp index 6139305..3862792 100644 --- a/src/graphics/model.cpp +++ b/src/graphics/model.cpp @@ -6,23 +6,52 @@ #include #include +#include #include #include "mesh.hpp" #include "model.hpp" #include "arrays.hpp" +#include "texture.hpp" using namespace sim::graphics; -static void proc_mesh(std::vector& meshes, aiMesh* mesh, const aiScene* scene) +struct proc_state { + unsigned int offset = 0; + + std::string base; std::vector vertices; std::vector indices; +}; + +static unsigned int proc_texture(const proc_state& state, aiMaterial* mat, aiTextureType type) +{ + if(mat->GetTextureCount(type) == 0) + { + return 0; + } + + aiString str; + mat->GetTexture(type, 0, &str); + + std::string filename(str.C_Str()); + std::replace(filename.begin(), filename.end(), '\\', '/'); + + return texture::load(state.base + "/" + filename); +} + +static void proc_mesh(proc_state& state, std::vector& meshes, aiMesh* mesh, const aiScene* scene) +{ + aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + unsigned int texid = proc_texture(state, material, aiTextureType_DIFFUSE); + unsigned int offset = state.offset; for(unsigned int i = 0; i < mesh->mNumVertices; i++) { arrays::vertex vertex; + vertex.texid = texid; vertex.pos = {mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z}; if(mesh->HasNormals()) @@ -30,7 +59,13 @@ static void proc_mesh(std::vector& meshes, aiMesh* mesh, const aiScene* sc vertex.normal = {mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z}; } - vertices.push_back(vertex); + if(mesh->mTextureCoords[0]) + { + vertex.texpos = {mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y}; + vertex.do_tex = 1; + } + + state.vertices.push_back(vertex); } for(unsigned int i = 0; i < mesh->mNumFaces; i++) @@ -39,35 +74,40 @@ static void proc_mesh(std::vector& meshes, aiMesh* mesh, const aiScene* sc for(unsigned int j = 0; j < face.mNumIndices; j++) { - indices.push_back(face.mIndices[j]); + state.indices.push_back(face.mIndices[j] + offset); } } - sim::graphics::mesh m; - m.set_vertices(&vertices[0], vertices.size(), GL_STATIC_DRAW); - m.set_indices(&indices[0], indices.size(), GL_STATIC_DRAW); - meshes.push_back(std::move(m)); + state.offset += mesh->mNumVertices; } -static void proc_node(std::vector& meshes, aiNode* node, const aiScene* scene) +static void proc_node(proc_state& state, std::vector& meshes, aiNode* node, const aiScene* scene) { for(size_t i = 0; i < node->mNumMeshes; i++) { aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; - proc_mesh(meshes, mesh, scene); + proc_mesh(state, meshes, mesh, scene); } for(size_t i = 0; i < node->mNumChildren; i++) { - proc_node(meshes, node->mChildren[i], scene); + proc_node(state, meshes, node->mChildren[i], scene); } } -void model::load(const char* path) +void model::load(std::string base, std::string filename) { + proc_state state {.base = base}; + std::string path = base + "/" + filename; Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs); - proc_node(meshes, scene->mRootNode, scene); + + const aiScene *scene = importer.ReadFile(path.c_str(), aiProcess_Triangulate | aiProcess_FlipUVs); + proc_node(state, meshes, scene->mRootNode, scene); + + sim::graphics::mesh m; + m.set_vertices(&state.vertices[0], state.vertices.size(), GL_STATIC_DRAW); + m.set_indices(&state.indices[0], state.indices.size(), GL_STATIC_DRAW); + meshes.push_back(std::move(m)); } void model::render() const diff --git a/src/graphics/model.hpp b/src/graphics/model.hpp index 8a15c52..86aa60e 100644 --- a/src/graphics/model.hpp +++ b/src/graphics/model.hpp @@ -4,6 +4,7 @@ #include "mesh.hpp" #include +#include namespace sim::graphics { @@ -12,7 +13,7 @@ struct model { std::vector meshes; - void load(const char* path); + void load(std::string base, std::string path); void render() const; }; diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp index f0bba1f..444607b 100644 --- a/src/graphics/shader.cpp +++ b/src/graphics/shader.cpp @@ -17,23 +17,23 @@ layout (location = 0) in sampler2D aTex; layout (location = 1) in vec2 aTexPos; layout (location = 2) in vec3 aPos; layout (location = 3) in vec3 aNormal; +layout (location = 4) in float aDoTex; uniform mat4 model; uniform mat4 projection; +out float do_tex; +out float brightness; out flat sampler2D tex; out vec2 texPos; -out float zVal; void main() { vec4 pos = model * vec4(aPos, 1.0); + vec3 cNormal = vec3(0.f, 0.f, 1.f) * mat3(model); - mat3 model_norm = mat3(model); - vec3 normal = aNormal; - vec3 cNormal = vec3(0.f, 0.f, 1.f) * model_norm; - - zVal = dot(normal, cNormal);// / (length(aNormal) * length(cNormal)); + brightness = dot(normalize(aNormal), normalize(cNormal)) * 0.25f + 0.75f; + do_tex = aDoTex; gl_Position = projection * pos; texPos = aTexPos; @@ -46,19 +46,19 @@ static const char* FRAGMENT_SHADER = R"( #version 460 core #extension GL_ARB_bindless_texture : require +in float do_tex; +in float brightness; in flat sampler2D tex; in vec2 texPos; -in float zVal; out vec4 FragColour; -uniform bool do_tex; uniform mat4 tex_mat; void main() { - vec4 texdata = do_tex ? texture2D(tex, texPos) : vec4(1); - FragColour = tex_mat * texdata * vec4(zVal, zVal, zVal, 1); + vec4 texdata = (do_tex > 0.5f) ? texture2D(tex, texPos) : vec4(1); + FragColour = tex_mat * texdata * vec4(vec3(brightness), 1); } )"; diff --git a/src/graphics/texture.cpp b/src/graphics/texture.cpp new file mode 100644 index 0000000..e7ca227 --- /dev/null +++ b/src/graphics/texture.cpp @@ -0,0 +1,76 @@ + +#include +#include +#include + +#include +#include + +#include "texture.hpp" + +using namespace sim::graphics; + +static std::unordered_map loaded; + +unsigned int texture::load(std::string path) +{ + const auto it = loaded.find(path); + + if(it != loaded.end()) + { + return it->second; + } + + int width, height, channels; + unsigned char* data = stbi_load(path.c_str(), &width, &height, &channels, 0); + + if(!data) + { + stbi_image_free(data); + throw std::runtime_error("Failed to load path: " + path); + } + + GLenum format, format_in; + switch(channels) + { + case 1: + format = GL_RED; + format_in = GL_R8; + break; + case 2: + format = GL_RG; + format_in = GL_RG8; + break; + case 3: + format = GL_RGB; + format_in = GL_RGB8; + break; + case 4: + format = GL_RGBA; + format_in = GL_RGBA8; + break; + } + + unsigned int texid; + + glCreateTextures(GL_TEXTURE_2D, 1, &texid); + glTextureStorage2D(texid, 1, format_in, width, height); + glTextureSubImage2D(texid, 0, 0, 0, width, height, format, GL_UNSIGNED_BYTE, data); + + stbi_image_free(data); + + glTextureParameteri(texid, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(texid, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(texid, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(texid, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glGenerateTextureMipmap(texid); + + unsigned int handle = glGetTextureHandleARB(texid); + glMakeTextureHandleResidentARB(handle); + + std::cout << "Loaded Image: " << path << "\n"; + + loaded[path] = handle; + return handle; +} + diff --git a/src/graphics/texture.hpp b/src/graphics/texture.hpp new file mode 100644 index 0000000..a29d157 --- /dev/null +++ b/src/graphics/texture.hpp @@ -0,0 +1,12 @@ + +#pragma once + +#include + +namespace sim::graphics::texture +{ + +unsigned int load(std::string path); + +}; + diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 01d267c..431c73a 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -35,8 +35,11 @@ void window::create() 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); + +#ifdef __APPLE__ + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true); +#endif win = glfwCreateWindow(800, 600, "FastNuclearSim", nullptr, nullptr); @@ -53,6 +56,7 @@ void window::create() glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); @@ -66,7 +70,7 @@ void window::create() shader::init_program(); - Model.load("teapot.obj"); + Model.load("Minimalistic Modern Office", "Minimalistic Modern Office.fbx"); glViewport(0, 0, 800, 600); } @@ -77,10 +81,10 @@ void window::loop() glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glm::mat4 mat_colour = { - 1, 1, 1, 1, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0 + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 }; camera::update(); @@ -90,11 +94,12 @@ void window::loop() double mouse_x, mouse_y; mouse::get(mouse_x, mouse_y); - mat_model = glm::translate(mat_model, glm::vec3(0.0f, 0.0f, -5.0f)); - mat_model = glm::rotate(mat_model, float(M_PI * 0.125), glm::vec3(1, 1, 1)); - mat_model = glm::scale(mat_model, glm::vec3(1, 1, 1) * 0.2f); + mat_model = glm::translate(mat_model, glm::vec3(0, -90, 0)); +// mat_model = glm::rotate(mat_model, float(M_PI * 0.125), glm::vec3(1, 1, 1)); + mat_model = glm::scale(mat_model, glm::vec3(15, 60, 15)); + mat_model = glm::rotate(mat_model, -float(M_PI) / 2, glm::vec3(0, 1, 0)); - glm::mat4 mat_projection = glm::perspective(float(M_PI * 0.25), (float)resize::get_aspect(), 0.1f, 100.f); + glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), (float)resize::get_aspect(), 0.1f, 200.f); glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &mat_colour[0][0]); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); diff --git a/src/stb/stb_image.cpp b/src/stb/stb_image.cpp new file mode 100644 index 0000000..36a258d --- /dev/null +++ b/src/stb/stb_image.cpp @@ -0,0 +1,4 @@ + +#define STB_IMAGE_IMPLEMENTATION +#include + From f14d96856483897ca20d42d9f1b1e3d89f1e0655 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Tue, 23 Jan 2024 22:48:22 +1100 Subject: [PATCH 07/43] added a scene --- src/graphics/camera.cpp | 15 ++++++++++----- src/graphics/texture.cpp | 6 +++--- src/graphics/window.cpp | 10 +++------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index 71c9aef..73c460a 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -13,7 +13,7 @@ using namespace sim::graphics; static double yaw = 0, pitch = 0; -static glm::vec<3, double> pos(56, 90, 8); +static glm::vec<3, double> pos(0, 0, 2); static glm::vec<3, double> velocity(0); void camera::rotate(double y, double p) @@ -35,7 +35,7 @@ void camera::move(double xoff, double yoff, double zoff) void camera::update() { glm::vec<2, double> off(0, 0); - double m = 0.01; + double m = 0.002; if(keyboard::is_pressed(GLFW_KEY_W)) off.y += 1; @@ -60,15 +60,15 @@ void camera::update() glm::vec<2, double> rotated = glm::vec<2, double>(off.x, off.y) * mat; bool on_ground = false; - velocity.z -= 0.00981; + velocity.z -= 0.000981; - if(pos.z + velocity.z < 3.5) + if(pos.z + velocity.z < 1.6) { on_ground = true; if(keyboard::is_pressed(GLFW_KEY_SPACE)) { - velocity.z += 0.4; + velocity.z += 0.04; } else @@ -84,6 +84,11 @@ void camera::update() velocity.x += rotated.x * m; velocity.y += rotated.y * m; + + if(std::abs(pos.x + velocity.x) > 2.9) + velocity.x = 0; + if(std::abs(pos.y + velocity.y) > 3.9) + velocity.y = 0; pos += velocity; velocity *= glm::vec<3, double>(on_ground ? 0.9 : 0.9999); diff --git a/src/graphics/texture.cpp b/src/graphics/texture.cpp index e7ca227..8a35abb 100644 --- a/src/graphics/texture.cpp +++ b/src/graphics/texture.cpp @@ -54,13 +54,13 @@ unsigned int texture::load(std::string path) unsigned int texid; glCreateTextures(GL_TEXTURE_2D, 1, &texid); - glTextureStorage2D(texid, 1, format_in, width, height); + glTextureStorage2D(texid, 8, format_in, width, height); glTextureSubImage2D(texid, 0, 0, 0, width, height, format, GL_UNSIGNED_BYTE, data); stbi_image_free(data); - glTextureParameteri(texid, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTextureParameteri(texid, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(texid, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTextureParameteri(texid, GL_TEXTURE_WRAP_T, GL_REPEAT); glTextureParameteri(texid, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTextureParameteri(texid, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glGenerateTextureMipmap(texid); diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 431c73a..6122107 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -70,7 +70,8 @@ void window::create() shader::init_program(); - Model.load("Minimalistic Modern Office", "Minimalistic Modern Office.fbx"); +// Model.load("Minimalistic Modern Office", "Minimalistic Modern Office.fbx"); + Model.load("../assets", "scene.obj"); glViewport(0, 0, 800, 600); } @@ -94,12 +95,7 @@ void window::loop() double mouse_x, mouse_y; mouse::get(mouse_x, mouse_y); - mat_model = glm::translate(mat_model, glm::vec3(0, -90, 0)); -// mat_model = glm::rotate(mat_model, float(M_PI * 0.125), glm::vec3(1, 1, 1)); - mat_model = glm::scale(mat_model, glm::vec3(15, 60, 15)); - mat_model = glm::rotate(mat_model, -float(M_PI) / 2, glm::vec3(0, 1, 0)); - - glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), (float)resize::get_aspect(), 0.1f, 200.f); + glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), (float)resize::get_aspect(), 0.01f, 20.f); glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &mat_colour[0][0]); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); From 761936b3e934a157a391e815859fb7033eca734a Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Tue, 23 Jan 2024 23:45:19 +1100 Subject: [PATCH 08/43] added plants --- src/graphics/model.cpp | 7 ++++--- src/graphics/model.hpp | 4 +++- src/graphics/window.cpp | 7 +++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/graphics/model.cpp b/src/graphics/model.cpp index 3862792..84755ea 100644 --- a/src/graphics/model.cpp +++ b/src/graphics/model.cpp @@ -23,6 +23,7 @@ struct proc_state std::string base; std::vector vertices; std::vector indices; + glm::vec<3, double> pos; }; static unsigned int proc_texture(const proc_state& state, aiMaterial* mat, aiTextureType type) @@ -52,7 +53,7 @@ static void proc_mesh(proc_state& state, std::vector& meshes, aiMesh* mesh arrays::vertex vertex; vertex.texid = texid; - vertex.pos = {mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z}; + vertex.pos = state.pos + glm::vec<3, double>(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z); if(mesh->HasNormals()) { @@ -95,9 +96,9 @@ static void proc_node(proc_state& state, std::vector& meshes, aiNode* node } } -void model::load(std::string base, std::string filename) +void model::load(std::string base, std::string filename, glm::vec<3, double> pos) { - proc_state state {.base = base}; + proc_state state {.base = base, .pos = pos}; std::string path = base + "/" + filename; Assimp::Importer importer; diff --git a/src/graphics/model.hpp b/src/graphics/model.hpp index 86aa60e..26eaa0f 100644 --- a/src/graphics/model.hpp +++ b/src/graphics/model.hpp @@ -3,6 +3,7 @@ #include "mesh.hpp" +#include #include #include @@ -13,7 +14,8 @@ struct model { std::vector meshes; - void load(std::string base, std::string path); + void load(std::string base, std::string path, glm::vec<3, double> offset); + void load(std::string base, std::string path) { load(base, path, {0, 0, 0}); } void render() const; }; diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 6122107..f4ea4bf 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -70,8 +70,11 @@ void window::create() shader::init_program(); -// Model.load("Minimalistic Modern Office", "Minimalistic Modern Office.fbx"); - Model.load("../assets", "scene.obj"); + Model.load("../assets/scene", "scene.obj"); + Model.load("../assets/AllPlants", "Plant3.obj", {2.5, 3.5, 0}); + Model.load("../assets/AllPlants", "Plant1.obj", {-2.5, -3.5, 0}); + Model.load("../assets/AllPlants", "Plant4.obj", {2, -3, 0}); + Model.load("../assets/AllPlants", "Plant5.obj", {-2, 3, 0}); glViewport(0, 0, 800, 600); } From ff8856983c2510ba1bd3c74b8ee5b0e95183dd68 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Tue, 23 Jan 2024 23:59:24 +1100 Subject: [PATCH 09/43] added a tv --- src/graphics/window.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index f4ea4bf..563342c 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -75,6 +75,7 @@ void window::create() Model.load("../assets/AllPlants", "Plant1.obj", {-2.5, -3.5, 0}); Model.load("../assets/AllPlants", "Plant4.obj", {2, -3, 0}); Model.load("../assets/AllPlants", "Plant5.obj", {-2, 3, 0}); + Model.load("../assets/monitor-scp", "Monitor.obj", {-3, 0, -2}); glViewport(0, 0, 800, 600); } From 6145059116e7c2881ab91f1ecbf9185d2194f8b7 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 24 Jan 2024 00:02:31 +1100 Subject: [PATCH 10/43] added attributions --- README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a5c581 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ + +# Credits +- [Monitor - SCP](https://skfb.ly/6RKYR) by Maxime66410 is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). +- [Potted House Plants](https://skfb.ly/opQN8) by FacultyManBruce is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). + From 7a2164c169180622ccc0491ad561c0105c23ea91 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 24 Jan 2024 18:34:04 +1100 Subject: [PATCH 11/43] added monitors, improved scene --- README.md | 1 - src/graphics/camera.cpp | 32 +++--- src/graphics/camera.hpp | 2 +- src/graphics/font.cpp | 72 ------------- src/graphics/font.hpp | 20 ---- src/graphics/{ => input}/keyboard.cpp | 6 +- src/graphics/{ => input}/keyboard.hpp | 0 src/graphics/{ => input}/mouse.cpp | 4 +- src/graphics/{ => input}/mouse.hpp | 0 src/graphics/{ => mesh}/arrays.cpp | 5 +- src/graphics/{ => mesh}/arrays.hpp | 4 - src/graphics/mesh/font.cpp | 141 ++++++++++++++++++++++++++ src/graphics/mesh/font.hpp | 15 +++ src/graphics/{ => mesh}/mesh.cpp | 34 +++++-- src/graphics/{ => mesh}/mesh.hpp | 14 ++- src/graphics/{ => mesh}/model.cpp | 43 +++----- src/graphics/{ => mesh}/texture.cpp | 0 src/graphics/{ => mesh}/texture.hpp | 0 src/graphics/model.hpp | 23 ----- src/graphics/shader.cpp | 10 +- src/graphics/shader.hpp | 1 - src/graphics/window.cpp | 58 ++++++----- src/math.hpp | 20 ++++ 23 files changed, 286 insertions(+), 219 deletions(-) delete mode 100644 src/graphics/font.cpp delete mode 100644 src/graphics/font.hpp rename src/graphics/{ => input}/keyboard.cpp (90%) rename src/graphics/{ => input}/keyboard.hpp (100%) rename src/graphics/{ => input}/mouse.cpp (91%) rename src/graphics/{ => input}/mouse.hpp (100%) rename src/graphics/{ => mesh}/arrays.cpp (85%) rename src/graphics/{ => mesh}/arrays.hpp (65%) create mode 100644 src/graphics/mesh/font.cpp create mode 100644 src/graphics/mesh/font.hpp rename src/graphics/{ => mesh}/mesh.cpp (56%) rename src/graphics/{ => mesh}/mesh.hpp (60%) rename src/graphics/{ => mesh}/model.cpp (60%) rename src/graphics/{ => mesh}/texture.cpp (100%) rename src/graphics/{ => mesh}/texture.hpp (100%) delete mode 100644 src/graphics/model.hpp create mode 100644 src/math.hpp diff --git a/README.md b/README.md index 5a5c581..bb6fc2b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # Credits -- [Monitor - SCP](https://skfb.ly/6RKYR) by Maxime66410 is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). - [Potted House Plants](https://skfb.ly/opQN8) by FacultyManBruce is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index 73c460a..a4c6e4f 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -3,7 +3,8 @@ #include #include "camera.hpp" -#include "keyboard.hpp" +#include "input/keyboard.hpp" +#include "../math.hpp" #include #include @@ -15,11 +16,12 @@ using namespace sim::graphics; static double yaw = 0, pitch = 0; static glm::vec<3, double> pos(0, 0, 2); static glm::vec<3, double> velocity(0); +static glm::mat4 camera_mat; void camera::rotate(double y, double p) { - yaw -= y * 0.05; - pitch += p * 0.05; + yaw += y * 0.05; + pitch -= p * 0.05; if(pitch < 0) pitch = 0; if(pitch > 180) pitch = 180; @@ -42,9 +44,9 @@ void camera::update() if(keyboard::is_pressed(GLFW_KEY_S)) off.y -= 1; if(keyboard::is_pressed(GLFW_KEY_A)) - off.x += 1; - if(keyboard::is_pressed(GLFW_KEY_D)) off.x -= 1; + if(keyboard::is_pressed(GLFW_KEY_D)) + off.x += 1; if(keyboard::is_pressed(GLFW_KEY_LEFT_SHIFT)) m *= 1.5; if(off.x != 0 || off.y != 0) @@ -74,6 +76,7 @@ void camera::update() else { velocity.z = 0; + pos.z = 1.6; } } @@ -91,17 +94,18 @@ void camera::update() velocity.y = 0; pos += velocity; - velocity *= glm::vec<3, double>(on_ground ? 0.9 : 0.9999); + velocity *= glm::vec<3, double>(on_ground ? 0.9 : 0.999); + + camera_mat = glm::mat4(1); + camera_mat = glm::rotate(camera_mat, (float)glm::radians(-pitch), glm::vec3(1, 0, 0)); + camera_mat = glm::rotate(camera_mat, (float)glm::radians(yaw), glm::vec3(0, 0, 1)); + camera_mat = glm::translate(camera_mat, glm::vec3(-pos.x, -pos.y, -pos.z)); + + std::cout << "at: " << pos << "\n"; } -glm::mat4 camera::get_model_matrix() +glm::mat4 camera::get_matrix() { - glm::mat4 mat(1); - - mat = glm::rotate(mat, (float)glm::radians(pitch), glm::vec3(1, 0, 0)); - mat = glm::rotate(mat, (float)glm::radians(yaw), glm::vec3(0, 0, 1)); - mat = glm::translate(mat, glm::vec3(pos.x, pos.y, pos.z)); - - return mat; + return camera_mat; } diff --git a/src/graphics/camera.hpp b/src/graphics/camera.hpp index 37ac263..0704ec4 100644 --- a/src/graphics/camera.hpp +++ b/src/graphics/camera.hpp @@ -7,7 +7,7 @@ namespace sim::graphics::camera { -glm::mat4 get_model_matrix(); +glm::mat4 get_matrix(); void rotate(double pitch, double yaw); void move(double x, double y, double z); void update(); diff --git a/src/graphics/font.cpp b/src/graphics/font.cpp deleted file mode 100644 index 82a6d2f..0000000 --- a/src/graphics/font.cpp +++ /dev/null @@ -1,72 +0,0 @@ - -#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); - glCreateTextures(GL_TEXTURE_2D, 128, texids); - - 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; - } - - 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 deleted file mode 100644 index 1665ff4..0000000 --- a/src/graphics/font.hpp +++ /dev/null @@ -1,20 +0,0 @@ - -#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/input/keyboard.cpp similarity index 90% rename from src/graphics/keyboard.cpp rename to src/graphics/input/keyboard.cpp index aa77087..09252cb 100644 --- a/src/graphics/keyboard.cpp +++ b/src/graphics/input/keyboard.cpp @@ -5,9 +5,9 @@ #include #include "keyboard.hpp" -#include "window.hpp" -#include "resize.hpp" -#include "camera.hpp" +#include "../window.hpp" +#include "../resize.hpp" +#include "../camera.hpp" using namespace sim::graphics; diff --git a/src/graphics/keyboard.hpp b/src/graphics/input/keyboard.hpp similarity index 100% rename from src/graphics/keyboard.hpp rename to src/graphics/input/keyboard.hpp diff --git a/src/graphics/mouse.cpp b/src/graphics/input/mouse.cpp similarity index 91% rename from src/graphics/mouse.cpp rename to src/graphics/input/mouse.cpp index 05d10fc..96a4a6a 100644 --- a/src/graphics/mouse.cpp +++ b/src/graphics/input/mouse.cpp @@ -3,8 +3,8 @@ #include #include "mouse.hpp" -#include "window.hpp" -#include "camera.hpp" +#include "../window.hpp" +#include "../camera.hpp" using namespace sim::graphics; diff --git a/src/graphics/mouse.hpp b/src/graphics/input/mouse.hpp similarity index 100% rename from src/graphics/mouse.hpp rename to src/graphics/input/mouse.hpp diff --git a/src/graphics/arrays.cpp b/src/graphics/mesh/arrays.cpp similarity index 85% rename from src/graphics/arrays.cpp rename to src/graphics/mesh/arrays.cpp index 8da8753..e85edea 100644 --- a/src/graphics/arrays.cpp +++ b/src/graphics/mesh/arrays.cpp @@ -4,7 +4,7 @@ #include -#include "shader.hpp" +#include "../shader.hpp" #include "arrays.hpp" #include "font.hpp" @@ -32,8 +32,5 @@ void arrays::vertex_attrib_pointers() glVertexAttribPointer(3, 3, GL_FLOAT, false, sizeof(v), ptr_diff(&v.normal, &v)); glEnableVertexAttribArray(3); - - glVertexAttribPointer(4, 1, GL_FLOAT, false, sizeof(v), ptr_diff(&v.do_tex, &v)); - glEnableVertexAttribArray(4); } diff --git a/src/graphics/arrays.hpp b/src/graphics/mesh/arrays.hpp similarity index 65% rename from src/graphics/arrays.hpp rename to src/graphics/mesh/arrays.hpp index e9995d6..b56ce7e 100644 --- a/src/graphics/arrays.hpp +++ b/src/graphics/mesh/arrays.hpp @@ -13,10 +13,6 @@ struct vertex glm::vec2 texpos = {0, 0}; glm::vec3 pos = {0, 0, 0}; glm::vec3 normal = {0, 0, 0}; - float do_tex = 0; - - vertex() { } - vertex(unsigned long texid, glm::vec2 texpos, glm::vec3 pos) : texid(texid), texpos(texpos), pos(pos) { } }; void vertex_attrib_pointers(); diff --git a/src/graphics/mesh/font.cpp b/src/graphics/mesh/font.cpp new file mode 100644 index 0000000..4945472 --- /dev/null +++ b/src/graphics/mesh/font.cpp @@ -0,0 +1,141 @@ + +#include +#include +#include +#include FT_FREETYPE_H + +#include +#include +#include + +#include "arrays.hpp" +#include "font.hpp" + +using namespace sim::graphics; + +struct character +{ + unsigned long handle; + float advance; + glm::vec2 size; + glm::vec2 bearing; +}; + +static character 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, "../assets/font/DroidSans.ttf", 0, &face)) + { + std::cout << "Error: failed to load freetype font\n"; + return; + } + + int size = 1024; + float m = 1.0f / size; + + FT_Set_Pixel_Sizes(face, 0, size); + + 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"; + } + + int width = face->glyph->bitmap.width; + int height = face->glyph->bitmap.rows; + int offx = face->glyph->bitmap_left; + int offy = face->glyph->bitmap_top; + + character& c = chars[i]; + c.advance = face->glyph->advance.x * m / 64.0; + c.size = {width * m, height * m}; + c.bearing = {offx * m, offy * m}; + + 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, width, height); + glTextureSubImage2D(texids[i], 0, 0, 0, width, height, 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; + } +} + +void font::generate(mesh& m, const char* text, double size) +{ + std::vector vertices; + std::vector indices; + + float x = 0, y = size; + unsigned int at = 0; + + for(unsigned int i = 0; text[i] != '\0'; i++) + { + char c = text[i]; + character ch = chars[c]; + + if(c == '\n') + { + x = 0; + y += size; + continue; + } + + if(ch.handle == 0) + { + x += ch.advance * size; + continue; + } + + unsigned int index[6] = { + at, at + 1, at + 3, + at, at + 3, at + 2 + }; + + float sx = x + ch.bearing.x * size; + float sy = y - ch.bearing.y * size; + float ex = sx + ch.size.x * size; + float ey = sy + ch.size.y * size; + + vertices.push_back(arrays::vertex(ch.handle, {0, 0}, {sx, sy, 0}, {0, 0, -1})); + vertices.push_back(arrays::vertex(ch.handle, {0, 1}, {sx, ey, 0}, {0, 0, -1})); + vertices.push_back(arrays::vertex(ch.handle, {1, 0}, {ex, sy, 0}, {0, 0, -1})); + vertices.push_back(arrays::vertex(ch.handle, {1, 1}, {ex, ey, 0}, {0, 0, -1})); + indices.insert(indices.end(), &index[0], &index[6]); + + at += 4; + x += ch.advance * size; + } + + m.set_vertices(&vertices[0], vertices.size(), GL_DYNAMIC_DRAW); + m.set_indices(&indices[0], indices.size(), GL_DYNAMIC_DRAW); +} + diff --git a/src/graphics/mesh/font.hpp b/src/graphics/mesh/font.hpp new file mode 100644 index 0000000..bff5902 --- /dev/null +++ b/src/graphics/mesh/font.hpp @@ -0,0 +1,15 @@ + +#pragma once + +#include "mesh.hpp" + +#include + +namespace sim::graphics::font +{ + +void init(); +void generate(mesh& m, const char* text, double size); + +}; + diff --git a/src/graphics/mesh.cpp b/src/graphics/mesh/mesh.cpp similarity index 56% rename from src/graphics/mesh.cpp rename to src/graphics/mesh/mesh.cpp index f561e96..016c3aa 100644 --- a/src/graphics/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -4,19 +4,25 @@ #include "mesh.hpp" #include "arrays.hpp" -#include +#include "../shader.hpp" +#include "../camera.hpp" using namespace sim::graphics; -mesh::mesh() +constexpr static void init(mesh* m) { - glGenVertexArrays(1, &vao); - glGenBuffers(1, &vbo); - glGenBuffers(1, &ebo); + if(m->vao != 0) + { + return; + } - glBindVertexArray(vao); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glGenVertexArrays(1, &m->vao); + glGenBuffers(1, &m->vbo); + glGenBuffers(1, &m->ebo); + + glBindVertexArray(m->vao); + glBindBuffer(GL_ARRAY_BUFFER, m->vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo); arrays::vertex_attrib_pointers(); } @@ -27,6 +33,9 @@ mesh::mesh(mesh&& o) ebo = o.ebo; vao = o.vao; size = o.size; + colour_matrix = o.colour_matrix; + model_matrix = o.model_matrix; + o.vbo = 0; o.ebo = 0; o.vao = 0; @@ -50,12 +59,17 @@ void mesh::set_indices(const unsigned int* data, size_t size, int mode) this->size = size; } -void mesh::bind() const +void mesh::bind() { + init(this); + + glm::mat4 m = camera::get_matrix() * model_matrix; + glUniformMatrix4fv(shader::gl_model, 1, false, &m[0][0]); + glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &colour_matrix[0][0]); glBindVertexArray(vao); } -void mesh::render() const +void mesh::render() { glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0); } diff --git a/src/graphics/mesh.hpp b/src/graphics/mesh/mesh.hpp similarity index 60% rename from src/graphics/mesh.hpp rename to src/graphics/mesh/mesh.hpp index b5f9b2a..0ff256e 100644 --- a/src/graphics/mesh.hpp +++ b/src/graphics/mesh/mesh.hpp @@ -1,7 +1,10 @@ #pragma once +#include + #include "arrays.hpp" +#include namespace sim::graphics { @@ -10,15 +13,20 @@ struct mesh { unsigned int vao = 0, vbo = 0, ebo = 0, size = 0; - mesh(); + glm::mat4 model_matrix {1.0f}; + glm::mat4 colour_matrix {1.0f}; + + constexpr mesh() { } + mesh(mesh&& o); mesh(const mesh& o) = delete; ~mesh(); - void bind() const; + void bind(); void set_vertices(const arrays::vertex* data, size_t size, int mode); void set_indices(const unsigned int* data, size_t size, int mode); - void render() const; + void load_model(std::string base, std::string path); + void render(); }; }; diff --git a/src/graphics/model.cpp b/src/graphics/mesh/model.cpp similarity index 60% rename from src/graphics/model.cpp rename to src/graphics/mesh/model.cpp index 84755ea..d6811ab 100644 --- a/src/graphics/model.cpp +++ b/src/graphics/mesh/model.cpp @@ -10,7 +10,6 @@ #include #include "mesh.hpp" -#include "model.hpp" #include "arrays.hpp" #include "texture.hpp" @@ -23,7 +22,6 @@ struct proc_state std::string base; std::vector vertices; std::vector indices; - glm::vec<3, double> pos; }; static unsigned int proc_texture(const proc_state& state, aiMaterial* mat, aiTextureType type) @@ -42,7 +40,7 @@ static unsigned int proc_texture(const proc_state& state, aiMaterial* mat, aiTex return texture::load(state.base + "/" + filename); } -static void proc_mesh(proc_state& state, std::vector& meshes, aiMesh* mesh, const aiScene* scene) +static void proc_mesh(proc_state& state, aiMesh* mesh, const aiScene* scene) { aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; unsigned int texid = proc_texture(state, material, aiTextureType_DIFFUSE); @@ -51,19 +49,21 @@ static void proc_mesh(proc_state& state, std::vector& meshes, aiMesh* mesh for(unsigned int i = 0; i < mesh->mNumVertices; i++) { arrays::vertex vertex; - + + auto [x, y, z] = mesh->mVertices[i]; + vertex.pos = {y, x, -z}; vertex.texid = texid; - vertex.pos = state.pos + glm::vec<3, double>(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z); if(mesh->HasNormals()) { - vertex.normal = {mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z}; + auto [x, y, z] = mesh->mNormals[i]; + vertex.normal = {y, x, -z}; } if(mesh->mTextureCoords[0]) { - vertex.texpos = {mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y}; - vertex.do_tex = 1; + auto [x, y, z] = mesh->mTextureCoords[0][i]; + vertex.texpos = {x, y}; } state.vertices.push_back(vertex); @@ -82,41 +82,30 @@ static void proc_mesh(proc_state& state, std::vector& meshes, aiMesh* mesh state.offset += mesh->mNumVertices; } -static void proc_node(proc_state& state, std::vector& meshes, aiNode* node, const aiScene* scene) +static void proc_node(proc_state& state, aiNode* node, const aiScene* scene) { for(size_t i = 0; i < node->mNumMeshes; i++) { aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; - proc_mesh(state, meshes, mesh, scene); + proc_mesh(state, mesh, scene); } for(size_t i = 0; i < node->mNumChildren; i++) { - proc_node(state, meshes, node->mChildren[i], scene); + proc_node(state, node->mChildren[i], scene); } } -void model::load(std::string base, std::string filename, glm::vec<3, double> pos) +void mesh::load_model(std::string base, std::string filename) { - proc_state state {.base = base, .pos = pos}; + proc_state state {.base = base}; std::string path = base + "/" + filename; Assimp::Importer importer; const aiScene *scene = importer.ReadFile(path.c_str(), aiProcess_Triangulate | aiProcess_FlipUVs); - proc_node(state, meshes, scene->mRootNode, scene); + proc_node(state, scene->mRootNode, scene); - sim::graphics::mesh m; - m.set_vertices(&state.vertices[0], state.vertices.size(), GL_STATIC_DRAW); - m.set_indices(&state.indices[0], state.indices.size(), GL_STATIC_DRAW); - meshes.push_back(std::move(m)); -} - -void model::render() const -{ - for(const mesh& m : meshes) - { - m.bind(); - m.render(); - } + set_vertices(&state.vertices[0], state.vertices.size(), GL_STATIC_DRAW); + set_indices(&state.indices[0], state.indices.size(), GL_STATIC_DRAW); } diff --git a/src/graphics/texture.cpp b/src/graphics/mesh/texture.cpp similarity index 100% rename from src/graphics/texture.cpp rename to src/graphics/mesh/texture.cpp diff --git a/src/graphics/texture.hpp b/src/graphics/mesh/texture.hpp similarity index 100% rename from src/graphics/texture.hpp rename to src/graphics/mesh/texture.hpp diff --git a/src/graphics/model.hpp b/src/graphics/model.hpp deleted file mode 100644 index 26eaa0f..0000000 --- a/src/graphics/model.hpp +++ /dev/null @@ -1,23 +0,0 @@ - -#pragma once - -#include "mesh.hpp" - -#include -#include -#include - -namespace sim::graphics -{ - -struct model -{ - std::vector meshes; - - void load(std::string base, std::string path, glm::vec<3, double> offset); - void load(std::string base, std::string path) { load(base, path, {0, 0, 0}); } - void render() const; -}; - -}; - diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp index 444607b..86290c8 100644 --- a/src/graphics/shader.cpp +++ b/src/graphics/shader.cpp @@ -17,12 +17,10 @@ layout (location = 0) in sampler2D aTex; layout (location = 1) in vec2 aTexPos; layout (location = 2) in vec3 aPos; layout (location = 3) in vec3 aNormal; -layout (location = 4) in float aDoTex; uniform mat4 model; uniform mat4 projection; -out float do_tex; out float brightness; out flat sampler2D tex; out vec2 texPos; @@ -33,7 +31,6 @@ void main() vec3 cNormal = vec3(0.f, 0.f, 1.f) * mat3(model); brightness = dot(normalize(aNormal), normalize(cNormal)) * 0.25f + 0.75f; - do_tex = aDoTex; gl_Position = projection * pos; texPos = aTexPos; @@ -46,7 +43,6 @@ static const char* FRAGMENT_SHADER = R"( #version 460 core #extension GL_ARB_bindless_texture : require -in float do_tex; in float brightness; in flat sampler2D tex; in vec2 texPos; @@ -57,8 +53,10 @@ uniform mat4 tex_mat; void main() { - vec4 texdata = (do_tex > 0.5f) ? texture2D(tex, texPos) : vec4(1); + vec4 texdata = texture2D(tex, texPos); FragColour = tex_mat * texdata * vec4(vec3(brightness), 1); + + if(FragColour.a == 0) discard; } )"; @@ -66,7 +64,6 @@ void main() static unsigned int prog_id; int shader::gl_tex_mat; -int shader::gl_do_tex; int shader::gl_model; int shader::gl_projection; @@ -102,7 +99,6 @@ unsigned int shader::init_program() } gl_tex_mat = glGetUniformLocation(prog_id, "tex_mat"); - gl_do_tex = glGetUniformLocation(prog_id, "do_tex"); gl_model = glGetUniformLocation(prog_id, "model"); gl_projection = glGetUniformLocation(prog_id, "projection"); diff --git a/src/graphics/shader.hpp b/src/graphics/shader.hpp index a68e99d..e144683 100644 --- a/src/graphics/shader.hpp +++ b/src/graphics/shader.hpp @@ -5,7 +5,6 @@ namespace sim::graphics::shader { extern int gl_tex_mat; -extern int gl_do_tex; extern int gl_model; extern int gl_projection; diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 563342c..639198f 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -8,21 +8,21 @@ #include -#include "model.hpp" -#include "arrays.hpp" -#include "keyboard.hpp" -#include "mouse.hpp" +#include "mesh/mesh.hpp" +#include "mesh/arrays.hpp" +#include "input/keyboard.hpp" +#include "input/mouse.hpp" #include "camera.hpp" #include "resize.hpp" #include "window.hpp" #include "shader.hpp" -#include "font.hpp" +#include "mesh/font.hpp" using namespace sim::graphics; static GLFWwindow* win; static bool win_should_close = false; -static model Model; +static mesh MeshScene, MeshText; void GLAPIENTRY cb_debug_message(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { @@ -44,6 +44,7 @@ void window::create() win = glfwCreateWindow(800, 600, "FastNuclearSim", nullptr, nullptr); glfwMakeContextCurrent(win); + glfwSwapInterval(1); GLenum err = glewInit(); @@ -70,43 +71,46 @@ void window::create() shader::init_program(); - Model.load("../assets/scene", "scene.obj"); - Model.load("../assets/AllPlants", "Plant3.obj", {2.5, 3.5, 0}); - Model.load("../assets/AllPlants", "Plant1.obj", {-2.5, -3.5, 0}); - Model.load("../assets/AllPlants", "Plant4.obj", {2, -3, 0}); - Model.load("../assets/AllPlants", "Plant5.obj", {-2, 3, 0}); - Model.load("../assets/monitor-scp", "Monitor.obj", {-3, 0, -2}); + MeshScene.bind(); + MeshScene.load_model("../assets", "scene.obj"); + + glm::mat4 mat = glm::mat4(1); + mat = glm::translate(mat, glm::vec3(-2.949, -1.7778, 3)); + mat = glm::rotate(mat, glm::radians(-90), glm::vec3(1, 0, 0)); + mat = glm::rotate(mat, glm::radians(-90), glm::vec3(0, 1, 0)); + + MeshText.model_matrix = mat; + MeshText.colour_matrix = { + 1, 0, 0, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; glViewport(0, 0, 800, 600); } void window::loop() { + MeshText.bind(); + font::generate(MeshText, "Hello, World!\nThis is cool!\n=)", 0.2); + glClearColor(0, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glm::mat4 mat_colour = { - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - }; - camera::update(); - glm::mat4 mat_model = camera::get_model_matrix(); - double mouse_x, mouse_y; mouse::get(mouse_x, mouse_y); - glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), (float)resize::get_aspect(), 0.01f, 20.f); - - glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &mat_colour[0][0]); + glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), 1.0f, 0.01f, 20.f); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); - glUniformMatrix4fv(shader::gl_model, 1, false, &mat_model[0][0]); - glUniform1i(shader::gl_do_tex, 0); - Model.render(); + MeshScene.bind(); + MeshScene.render(); + + MeshText.bind(); + MeshText.render(); glfwSwapBuffers(win); glfwPollEvents(); diff --git a/src/math.hpp b/src/math.hpp new file mode 100644 index 0000000..53cb189 --- /dev/null +++ b/src/math.hpp @@ -0,0 +1,20 @@ + +#pragma once + +#include +#include + +template +std::ostream& operator<<(std::ostream& o, const glm::vec& v) +{ + o << "{"; + + for(int i = 0; i < N - 1; i++) + { + o << v[i] << ", "; + } + + o << v[N - 1] << "}"; + return o; +} + From 14f20ae693e85c683f2393c78b3b770bc5fa1cab Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 24 Jan 2024 18:37:55 +1100 Subject: [PATCH 12/43] updated gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2475ac7..e27ce7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build +*.blend1 From c4701e58611f3cb3309617f6e51bde0a71ae3bff Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 24 Jan 2024 18:38:44 +1100 Subject: [PATCH 13/43] remove position line --- src/graphics/camera.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index a4c6e4f..1e8653a 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -100,8 +100,6 @@ void camera::update() camera_mat = glm::rotate(camera_mat, (float)glm::radians(-pitch), glm::vec3(1, 0, 0)); camera_mat = glm::rotate(camera_mat, (float)glm::radians(yaw), glm::vec3(0, 0, 1)); camera_mat = glm::translate(camera_mat, glm::vec3(-pos.x, -pos.y, -pos.z)); - - std::cout << "at: " << pos << "\n"; } glm::mat4 camera::get_matrix() From a62e309fb65b5a879a39a5fd9bdc0f19866a3067 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 24 Jan 2024 18:49:00 +1100 Subject: [PATCH 14/43] optimised font texture sizes --- src/graphics/mesh/font.cpp | 2 +- src/graphics/window.cpp | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/graphics/mesh/font.cpp b/src/graphics/mesh/font.cpp index 4945472..960519a 100644 --- a/src/graphics/mesh/font.cpp +++ b/src/graphics/mesh/font.cpp @@ -40,7 +40,7 @@ void font::init() return; } - int size = 1024; + int size = 256; float m = 1.0f / size; FT_Set_Pixel_Sizes(face, 0, size); diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 639198f..585ce9f 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -75,13 +75,13 @@ void window::create() MeshScene.load_model("../assets", "scene.obj"); glm::mat4 mat = glm::mat4(1); - mat = glm::translate(mat, glm::vec3(-2.949, -1.7778, 3)); + mat = glm::translate(mat, glm::vec3(-2.949, -1.7778 + 0.05, 3 - 0.05)); mat = glm::rotate(mat, glm::radians(-90), glm::vec3(1, 0, 0)); mat = glm::rotate(mat, glm::radians(-90), glm::vec3(0, 1, 0)); MeshText.model_matrix = mat; MeshText.colour_matrix = { - 1, 0, 0, 1, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -93,16 +93,12 @@ void window::create() void window::loop() { MeshText.bind(); - font::generate(MeshText, "Hello, World!\nThis is cool!\n=)", 0.2); + font::generate(MeshText, "Hello, World!\nThis is cool!\n=)", 0.1); glClearColor(0, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); camera::update(); - - double mouse_x, mouse_y; - mouse::get(mouse_x, mouse_y); - glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), 1.0f, 0.01f, 20.f); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); From 06d5158356e9215072f1ba64b4866adf61ae33bb Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 24 Jan 2024 22:01:33 +1100 Subject: [PATCH 15/43] added baked textures --- src/graphics/camera.cpp | 29 ++++++++++++++++------------- src/graphics/camera.hpp | 2 +- src/graphics/window.cpp | 7 +++---- src/main.cpp | 18 ++++++++++++++++++ 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index 1e8653a..4bdf2a4 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -6,6 +6,7 @@ #include "input/keyboard.hpp" #include "../math.hpp" +#include #include #include #include @@ -34,10 +35,10 @@ void camera::move(double xoff, double yoff, double zoff) pos.z += zoff; } -void camera::update() +void camera::update(double dt) { glm::vec<2, double> off(0, 0); - double m = 0.002; + double m = 30; if(keyboard::is_pressed(GLFW_KEY_W)) off.y += 1; @@ -50,7 +51,7 @@ void camera::update() if(keyboard::is_pressed(GLFW_KEY_LEFT_SHIFT)) m *= 1.5; if(off.x != 0 || off.y != 0) - off /= std::sqrt(off.x * off.x + off.y * off.y); + off = glm::normalize(off); double angle = glm::radians(yaw); @@ -62,15 +63,15 @@ void camera::update() glm::vec<2, double> rotated = glm::vec<2, double>(off.x, off.y) * mat; bool on_ground = false; - velocity.z -= 0.000981; + velocity.z -= 9.81 * dt; - if(pos.z + velocity.z < 1.6) + if(pos.z + velocity.z * dt < 1.6) { on_ground = true; if(keyboard::is_pressed(GLFW_KEY_SPACE)) { - velocity.z += 0.04; + velocity.z = 3.5; } else @@ -85,16 +86,18 @@ void camera::update() m = 0; } - velocity.x += rotated.x * m; - velocity.y += rotated.y * m; - - if(std::abs(pos.x + velocity.x) > 2.9) + velocity.x += rotated.x * m * dt; + velocity.y += rotated.y * m * dt; + + if(std::abs(pos.x + velocity.x * dt) > 2.9) velocity.x = 0; - if(std::abs(pos.y + velocity.y) > 3.9) + if(std::abs(pos.y + velocity.y * dt) > 3.9) velocity.y = 0; - pos += velocity; - velocity *= glm::vec<3, double>(on_ground ? 0.9 : 0.999); + float m2 = std::pow(0.5, dt / (on_ground ? 0.05 : 1)); + + pos += velocity * dt; + velocity *= m2; camera_mat = glm::mat4(1); camera_mat = glm::rotate(camera_mat, (float)glm::radians(-pitch), glm::vec3(1, 0, 0)); diff --git a/src/graphics/camera.hpp b/src/graphics/camera.hpp index 0704ec4..93ddc1c 100644 --- a/src/graphics/camera.hpp +++ b/src/graphics/camera.hpp @@ -10,7 +10,7 @@ namespace sim::graphics::camera glm::mat4 get_matrix(); void rotate(double pitch, double yaw); void move(double x, double y, double z); -void update(); +void update(double dt); }; diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 585ce9f..e9a3c54 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -95,12 +95,11 @@ void window::loop() MeshText.bind(); font::generate(MeshText, "Hello, World!\nThis is cool!\n=)", 0.1); - glClearColor(0, 0, 0, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - camera::update(); glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), 1.0f, 0.01f, 20.f); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); + + glClearColor(0, 0, 0, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); MeshScene.bind(); MeshScene.render(); diff --git a/src/main.cpp b/src/main.cpp index 3cf4dcf..339c057 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,14 +1,32 @@ +#include + #include "graphics/window.hpp" +#include "graphics/camera.hpp" using namespace sim; +unsigned long get_now() +{ + struct timeval tv; + gettimeofday(&tv, nullptr); + return (unsigned long)tv.tv_sec * 1000000 + tv.tv_usec; +} + int main() { graphics::window::create(); + long clock = get_now(); + while(!graphics::window::should_close()) { + long now = get_now(); + long passed = now - clock; + double dt = (double)passed / 1e6; + clock += passed; + + graphics::camera::update(dt); graphics::window::loop(); } From a74d81b25b3714dfeeefa72032ae0f7dba590c7f Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Thu, 25 Jan 2024 00:35:39 +1100 Subject: [PATCH 16/43] baked more textures --- src/graphics/window.cpp | 2 +- src/main.cpp | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index e9a3c54..32bdfba 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -72,7 +72,7 @@ void window::create() shader::init_program(); MeshScene.bind(); - MeshScene.load_model("../assets", "scene.obj"); + MeshScene.load_model("../assets/scene", "scene.obj"); glm::mat4 mat = glm::mat4(1); mat = glm::translate(mat, glm::vec3(-2.949, -1.7778 + 0.05, 3 - 0.05)); diff --git a/src/main.cpp b/src/main.cpp index 339c057..2a6c134 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,20 @@ #include +#include +#include +#include + +#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 "graphics/window.hpp" #include "graphics/camera.hpp" @@ -15,6 +29,9 @@ unsigned long get_now() int main() { + std::random_device rd; + std::mt19937 rand(rd()); + graphics::window::create(); long clock = get_now(); From 58860190c2d6d87ac15540af84917f4de821accc Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Fri, 26 Jan 2024 00:30:14 +1100 Subject: [PATCH 17/43] add monitors =) --- .gitignore | 1 + src/graphics/camera.cpp | 7 ++- src/graphics/mesh/arrays.cpp | 2 +- src/graphics/mesh/arrays.hpp | 3 +- src/graphics/mesh/font.cpp | 8 +-- src/graphics/mesh/font.hpp | 9 ++++ src/graphics/mesh/mesh.cpp | 3 ++ src/graphics/mesh/model.cpp | 96 +++++++++++++++++++++++++++------- src/graphics/mesh/texture.cpp | 47 +++++++++++------ src/graphics/mesh/texture.hpp | 2 + src/graphics/resize.cpp | 6 +-- src/graphics/resize.hpp | 4 +- src/graphics/shader.cpp | 4 +- src/graphics/window.cpp | 56 ++++++++++++++++++-- src/graphics/window.hpp | 2 +- src/main.cpp | 37 +++---------- src/parts.cpp | 34 ++++++++++++ src/parts.hpp | 20 +++++++ src/reactor/coolant/vessel.cpp | 13 ----- src/reactor/coolant/vessel.hpp | 15 +++++- 20 files changed, 271 insertions(+), 98 deletions(-) create mode 100644 src/parts.cpp create mode 100644 src/parts.hpp diff --git a/.gitignore b/.gitignore index e27ce7d..828958c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build *.blend1 +assets/scene-baked.glb diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index 4bdf2a4..b855ae5 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -89,9 +89,12 @@ void camera::update(double dt) velocity.x += rotated.x * m * dt; velocity.y += rotated.y * m * dt; - if(std::abs(pos.x + velocity.x * dt) > 2.9) + double nx = pos.x + velocity.x * dt; + double ny = pos.y + velocity.y * dt; + + if(nx > 8.9 || nx < -2.9) velocity.x = 0; - if(std::abs(pos.y + velocity.y * dt) > 3.9) + if(std::abs(ny) > 3.9) velocity.y = 0; float m2 = std::pow(0.5, dt / (on_ground ? 0.05 : 1)); diff --git a/src/graphics/mesh/arrays.cpp b/src/graphics/mesh/arrays.cpp index e85edea..63023f3 100644 --- a/src/graphics/mesh/arrays.cpp +++ b/src/graphics/mesh/arrays.cpp @@ -27,7 +27,7 @@ void arrays::vertex_attrib_pointers() 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)); + glVertexAttribPointer(2, 4, GL_FLOAT, false, sizeof(v), ptr_diff(&v.pos, &v)); glEnableVertexAttribArray(2); glVertexAttribPointer(3, 3, GL_FLOAT, false, sizeof(v), ptr_diff(&v.normal, &v)); diff --git a/src/graphics/mesh/arrays.hpp b/src/graphics/mesh/arrays.hpp index b56ce7e..2e42132 100644 --- a/src/graphics/mesh/arrays.hpp +++ b/src/graphics/mesh/arrays.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace sim::graphics::arrays { @@ -11,7 +12,7 @@ struct vertex { unsigned long texid = 0; glm::vec2 texpos = {0, 0}; - glm::vec3 pos = {0, 0, 0}; + glm::vec4 pos = {0, 0, 0, 1}; glm::vec3 normal = {0, 0, 0}; }; diff --git a/src/graphics/mesh/font.cpp b/src/graphics/mesh/font.cpp index 960519a..147d39e 100644 --- a/src/graphics/mesh/font.cpp +++ b/src/graphics/mesh/font.cpp @@ -125,10 +125,10 @@ void font::generate(mesh& m, const char* text, double size) float ex = sx + ch.size.x * size; float ey = sy + ch.size.y * size; - vertices.push_back(arrays::vertex(ch.handle, {0, 0}, {sx, sy, 0}, {0, 0, -1})); - vertices.push_back(arrays::vertex(ch.handle, {0, 1}, {sx, ey, 0}, {0, 0, -1})); - vertices.push_back(arrays::vertex(ch.handle, {1, 0}, {ex, sy, 0}, {0, 0, -1})); - vertices.push_back(arrays::vertex(ch.handle, {1, 1}, {ex, ey, 0}, {0, 0, -1})); + vertices.push_back(arrays::vertex(ch.handle, {0, 0}, {sx, sy, 0, 1}, {0, 0, -1})); + vertices.push_back(arrays::vertex(ch.handle, {0, 1}, {sx, ey, 0, 1}, {0, 0, -1})); + vertices.push_back(arrays::vertex(ch.handle, {1, 0}, {ex, sy, 0, 1}, {0, 0, -1})); + vertices.push_back(arrays::vertex(ch.handle, {1, 1}, {ex, ey, 0, 1}, {0, 0, -1})); indices.insert(indices.end(), &index[0], &index[6]); at += 4; diff --git a/src/graphics/mesh/font.hpp b/src/graphics/mesh/font.hpp index bff5902..7af4f77 100644 --- a/src/graphics/mesh/font.hpp +++ b/src/graphics/mesh/font.hpp @@ -4,6 +4,7 @@ #include "mesh.hpp" #include +#include namespace sim::graphics::font { @@ -11,5 +12,13 @@ namespace sim::graphics::font void init(); void generate(mesh& m, const char* text, double size); +template +void generate(mesh& m, const char* header, T* item, double size) +{ + std::stringstream ss; + ss << header << *item; + generate(m, ss.str().c_str(), size); +} + }; diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp index 016c3aa..3a1ed76 100644 --- a/src/graphics/mesh/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -66,7 +66,10 @@ void mesh::bind() glm::mat4 m = camera::get_matrix() * model_matrix; glUniformMatrix4fv(shader::gl_model, 1, false, &m[0][0]); glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &colour_matrix[0][0]); + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); } void mesh::render() diff --git a/src/graphics/mesh/model.cpp b/src/graphics/mesh/model.cpp index d6811ab..1735f56 100644 --- a/src/graphics/mesh/model.cpp +++ b/src/graphics/mesh/model.cpp @@ -5,7 +5,9 @@ #include #include #include +#include +#include #include #include @@ -22,28 +24,45 @@ struct proc_state std::string base; std::vector vertices; std::vector indices; + std::unordered_map handles; }; -static unsigned int proc_texture(const proc_state& state, aiMaterial* mat, aiTextureType type) +static unsigned int proc_texture(const proc_state& state, aiMaterial* mat, const aiScene* scene) { - if(mat->GetTextureCount(type) == 0) + for(int i = 0; i < 0x0d; i++) { - return 0; + aiTextureType type = (aiTextureType)i; + + if(mat->GetTextureCount(type) == 0) + { + continue; + } + + aiString str; + mat->GetTexture(type, 0, &str); + + const aiTexture* tex = scene->GetEmbeddedTexture(str.C_Str()); + + if(tex != nullptr) + { + unsigned int handle = state.handles.find(tex)->second; + std::cout << "Using preloaded texture: " << tex->mFilename.C_Str() << "\n"; + return handle; + } + + std::string filename(str.C_Str()); + std::replace(filename.begin(), filename.end(), '\\', '/'); + + return texture::load(state.base + "/" + filename); } - aiString str; - mat->GetTexture(type, 0, &str); - - std::string filename(str.C_Str()); - std::replace(filename.begin(), filename.end(), '\\', '/'); - - return texture::load(state.base + "/" + filename); + return 0; } -static void proc_mesh(proc_state& state, aiMesh* mesh, const aiScene* scene) +static void proc_mesh(proc_state& state, glm::mat4 mat, aiMesh* mesh, const aiScene* scene) { aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; - unsigned int texid = proc_texture(state, material, aiTextureType_DIFFUSE); + unsigned int texid = proc_texture(state, material, scene); unsigned int offset = state.offset; for(unsigned int i = 0; i < mesh->mNumVertices; i++) @@ -51,13 +70,13 @@ static void proc_mesh(proc_state& state, aiMesh* mesh, const aiScene* scene) arrays::vertex vertex; auto [x, y, z] = mesh->mVertices[i]; - vertex.pos = {y, x, -z}; + vertex.pos = glm::vec4(x, y, z, 1) * mat; vertex.texid = texid; if(mesh->HasNormals()) { auto [x, y, z] = mesh->mNormals[i]; - vertex.normal = {y, x, -z}; + vertex.normal = glm::vec3(x, y, z) * glm::mat3(mat); } if(mesh->mTextureCoords[0]) @@ -82,20 +101,47 @@ static void proc_mesh(proc_state& state, aiMesh* mesh, const aiScene* scene) state.offset += mesh->mNumVertices; } -static void proc_node(proc_state& state, aiNode* node, const aiScene* scene) +static void proc_node(proc_state& state, glm::mat4 mat, aiNode* node, const aiScene* scene) { + auto m = node->mTransformation; + mat = glm::mat4( + m.a1, m.a2, m.a3, m.a4, + m.b1, m.b2, m.b3, m.b4, + m.c1, m.c2, m.c3, m.c4, + m.d1, m.d2, m.d3, m.d4 + ) * mat; + for(size_t i = 0; i < node->mNumMeshes; i++) { aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; - proc_mesh(state, mesh, scene); + proc_mesh(state, mat, mesh, scene); } for(size_t i = 0; i < node->mNumChildren; i++) { - proc_node(state, node->mChildren[i], scene); + proc_node(state, mat, node->mChildren[i], scene); } } +static unsigned int proc_embedded_texture(aiTexture* tex) +{ + std::cout << "Loading embedded data: " << tex->mFilename.C_Str() << "\n"; + + if(tex->mHeight == 0) + { + return texture::load_mem((unsigned char*)tex->pcData, tex->mWidth); + } + + // swizzle each pixel to get RGBA + for(int i = 0; i < tex->mWidth * tex->mHeight; i++) + { + aiTexel t = tex->pcData[i]; + tex->pcData[i] = {t.r, t.g, t.b, t.a}; + } + + return texture::load_mem((unsigned char*)tex->pcData, tex->mWidth, tex->mHeight, 4); +} + void mesh::load_model(std::string base, std::string filename) { proc_state state {.base = base}; @@ -103,7 +149,21 @@ void mesh::load_model(std::string base, std::string filename) Assimp::Importer importer; const aiScene *scene = importer.ReadFile(path.c_str(), aiProcess_Triangulate | aiProcess_FlipUVs); - proc_node(state, scene->mRootNode, scene); + + if(scene == nullptr) + { + std::cerr << "AssImp: Error loading model\n"; + return; + } + + for(int i = 0; i < scene->mNumTextures; i++) + { + aiTexture* tex = scene->mTextures[i]; + unsigned int handle = proc_embedded_texture(tex); + state.handles[tex] = handle; + } + + proc_node(state, glm::mat4(1), scene->mRootNode, scene); set_vertices(&state.vertices[0], state.vertices.size(), GL_STATIC_DRAW); set_indices(&state.indices[0], state.indices.size(), GL_STATIC_DRAW); diff --git a/src/graphics/mesh/texture.cpp b/src/graphics/mesh/texture.cpp index 8a35abb..5f933c8 100644 --- a/src/graphics/mesh/texture.cpp +++ b/src/graphics/mesh/texture.cpp @@ -12,22 +12,11 @@ using namespace sim::graphics; static std::unordered_map loaded; -unsigned int texture::load(std::string path) +unsigned int texture::load_mem(const unsigned char* data, int width, int height, int channels) { - const auto it = loaded.find(path); - - if(it != loaded.end()) - { - return it->second; - } - - int width, height, channels; - unsigned char* data = stbi_load(path.c_str(), &width, &height, &channels, 0); - if(!data) { - stbi_image_free(data); - throw std::runtime_error("Failed to load path: " + path); + return 0; } GLenum format, format_in; @@ -57,8 +46,6 @@ unsigned int texture::load(std::string path) glTextureStorage2D(texid, 8, format_in, width, height); glTextureSubImage2D(texid, 0, 0, 0, width, height, format, GL_UNSIGNED_BYTE, data); - stbi_image_free(data); - glTextureParameteri(texid, GL_TEXTURE_WRAP_S, GL_REPEAT); glTextureParameteri(texid, GL_TEXTURE_WRAP_T, GL_REPEAT); glTextureParameteri(texid, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -67,6 +54,36 @@ unsigned int texture::load(std::string path) unsigned int handle = glGetTextureHandleARB(texid); glMakeTextureHandleResidentARB(handle); + return handle; +} + +unsigned int texture::load_mem(const unsigned char* filedata, size_t len) +{ + int width, height, channels; + unsigned char* data = stbi_load_from_memory(filedata, len, &width, &height, &channels, 0); + unsigned int handle = load_mem(data, width, height, channels); + stbi_image_free(data); + return handle; +} + +unsigned int texture::load(std::string path) +{ + const auto it = loaded.find(path); + + if(it != loaded.end()) + { + return it->second; + } + + int width, height, channels; + unsigned char* data = stbi_load(path.c_str(), &width, &height, &channels, 0); + unsigned int handle = load_mem(data, width, height, channels); + stbi_image_free(data); + + if(handle == 0) + { + throw std::runtime_error("Failed to load path: " + path); + } std::cout << "Loaded Image: " << path << "\n"; diff --git a/src/graphics/mesh/texture.hpp b/src/graphics/mesh/texture.hpp index a29d157..d8b402d 100644 --- a/src/graphics/mesh/texture.hpp +++ b/src/graphics/mesh/texture.hpp @@ -7,6 +7,8 @@ namespace sim::graphics::texture { unsigned int load(std::string path); +unsigned int load_mem(const unsigned char* data, int width, int height, int channels); +unsigned int load_mem(const unsigned char* data, size_t len); }; diff --git a/src/graphics/resize.cpp b/src/graphics/resize.cpp index 238292a..cf562c0 100644 --- a/src/graphics/resize.cpp +++ b/src/graphics/resize.cpp @@ -17,14 +17,14 @@ static int win_restore_h; static int win_restore_x; static int win_restore_y; -glm::vec2 resize::get_size() +glm::vec<2, int> resize::get_size() { return {win_w, win_h}; } -double resize::get_aspect() +float resize::get_aspect() { - return win_w / win_h; + return (float)win_w / (float)win_h; } void resize::toggle_fullscreen() diff --git a/src/graphics/resize.hpp b/src/graphics/resize.hpp index ca060e8..ae7c3a8 100644 --- a/src/graphics/resize.hpp +++ b/src/graphics/resize.hpp @@ -8,8 +8,8 @@ namespace sim::graphics::resize void init(); void toggle_fullscreen(); -glm::vec2 get_size(); -double get_aspect(); +glm::vec<2, int> get_size(); +float get_aspect(); }; diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp index 86290c8..f893e93 100644 --- a/src/graphics/shader.cpp +++ b/src/graphics/shader.cpp @@ -15,7 +15,7 @@ static const char* VERTEX_SHADER = R"( layout (location = 0) in sampler2D aTex; layout (location = 1) in vec2 aTexPos; -layout (location = 2) in vec3 aPos; +layout (location = 2) in vec4 aPos; layout (location = 3) in vec3 aNormal; uniform mat4 model; @@ -27,7 +27,7 @@ out vec2 texPos; void main() { - vec4 pos = model * vec4(aPos, 1.0); + vec4 pos = model * aPos; vec3 cNormal = vec3(0.f, 0.f, 1.f) * mat3(model); brightness = dot(normalize(aNormal), normalize(cNormal)) * 0.25f + 0.75f; diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 6ce2463..c93fec6 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -17,12 +17,15 @@ #include "window.hpp" #include "shader.hpp" #include "mesh/font.hpp" +#include "../parts.hpp" using namespace sim::graphics; static GLFWwindow* win; static bool win_should_close = false; + static mesh MeshScene, MeshText; +static mesh MeshMon1, MeshMon2, MeshMon3; void GLAPIENTRY cb_debug_message(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { @@ -75,7 +78,7 @@ void window::create() shader::init_program(); MeshScene.bind(); - MeshScene.load_model("../assets/scene", "scene.obj"); + MeshScene.load_model("../assets", "scene-baked.glb"); glm::mat4 mat = glm::mat4(1); mat = glm::translate(mat, glm::vec3(-2.949, -1.7778 + 0.05, 3 - 0.05)); @@ -90,15 +93,53 @@ void window::create() 0, 0, 0, 0 }; + mat = glm::mat4(1); + mat = glm::translate(mat, glm::vec3(-1.5 + 0.05, 3.949, 3 - 0.05)); + mat = glm::rotate(mat, glm::radians(-90), glm::vec3(1, 0, 0)); + + MeshMon1.model_matrix = mat; + MeshMon1.colour_matrix = { + 1, 0.5, 0.5, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + mat = glm::translate(glm::mat4(1), glm::vec3(2.5, 0, 0)) * mat; + + MeshMon2.model_matrix = mat; + MeshMon2.colour_matrix = { + 0.5, 1, 0.5, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + mat = glm::translate(glm::mat4(1), glm::vec3(2.5, 0, 0)) * mat; + + MeshMon3.model_matrix = mat; + MeshMon3.colour_matrix = { + 0.5, 0.5, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + glViewport(0, 0, 800, 600); } -void window::loop(const char* str) +void window::loop() { MeshText.bind(); - font::generate(MeshText, str, 0.1); + font::generate(MeshText, "Reactor Core\n\nTODO", 0.1); + MeshMon1.bind(); + font::generate(MeshMon1, "Reactor Vessel\n\n", parts::vessel, 0.1); + MeshMon2.bind(); + font::generate(MeshMon2, "Steam Valve\n\n", parts::valve, 0.1); + MeshMon3.bind(); + font::generate(MeshMon3, "Coolant Pump\n\n", parts::pump, 0.1); - glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), 1.0f, 0.01f, 20.f); + glm::mat4 mat_projection = glm::perspective(glm::radians(80.0f), resize::get_aspect(), 0.01f, 20.f); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); glClearColor(0, 0, 0, 1.0f); @@ -106,9 +147,14 @@ void window::loop(const char* str) MeshScene.bind(); MeshScene.render(); - MeshText.bind(); MeshText.render(); + MeshMon1.bind(); + MeshMon1.render(); + MeshMon2.bind(); + MeshMon2.render(); + MeshMon3.bind(); + MeshMon3.render(); glfwSwapBuffers(win); glfwPollEvents(); diff --git a/src/graphics/window.hpp b/src/graphics/window.hpp index a55c2a3..3b031fd 100644 --- a/src/graphics/window.hpp +++ b/src/graphics/window.hpp @@ -8,7 +8,7 @@ namespace sim::graphics::window void create(); bool should_close(); -void loop(const char* str); +void loop(); void destroy(); void close(); diff --git a/src/main.cpp b/src/main.cpp index dd32fde..f1466fd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,11 +5,6 @@ #include #include -#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" @@ -18,6 +13,8 @@ #include "graphics/window.hpp" #include "graphics/camera.hpp" +#include "parts.hpp" + using namespace sim; unsigned long get_now() @@ -31,22 +28,8 @@ int main() { std::random_device rd; std::mt19937 rand(rd()); - - sim::reactor::coolant::vessel vessel(8, 10, 300, 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); + parts::init(); graphics::window::create(); long clock = get_now(); @@ -58,17 +41,13 @@ int main() double dt = (double)passed / 1e6; clock += passed; - std::stringstream ss; - - reactor.update(rand, dt); - pump.update(dt); - valve.update(dt); - vessel.update(dt); - - ss << "Reactor Vessel\n\n" << vessel; + parts::reactor->update(rand, dt); + parts::pump->update(dt); + parts::valve->update(dt); + parts::vessel->update(dt); graphics::camera::update(dt); - graphics::window::loop(ss.str().c_str()); + graphics::window::loop(); } graphics::window::destroy(); diff --git a/src/parts.cpp b/src/parts.cpp new file mode 100644 index 0000000..e8069fe --- /dev/null +++ b/src/parts.cpp @@ -0,0 +1,34 @@ + +#include "parts.hpp" + +#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" + +using namespace sim; + +reactor::coolant::vessel* parts::vessel; +reactor::reactor<5, 5>* parts::reactor; +coolant::valve* parts::valve; +coolant::pump* parts::pump; + +void parts::init() +{ + vessel = new reactor::coolant::vessel(8, 10, 300, sim::coolant::WATER); + reactor = new reactor::reactor<5, 5>(sim::reactor::builder<5, 5>( + reactor::fuel::fuel_rod(2000, 4000), + reactor::control::control_rod(*vessel, 10000, 1), + reactor::coolant::pipe(*vessel), { + "#C#C#", + "CFCFC", + "#C#C#", + "CFCFC", + "#C#C#" + })); + + valve = new coolant::valve(*vessel, 1, 500); + pump = new coolant::pump(*vessel, 1e4, 15); +} + diff --git a/src/parts.hpp b/src/parts.hpp new file mode 100644 index 0000000..f52c5ad --- /dev/null +++ b/src/parts.hpp @@ -0,0 +1,20 @@ + +#pragma once + +#include "reactor/coolant/vessel.hpp" +#include "reactor/reactor.hpp" +#include "coolant/pump.hpp" +#include "coolant/valve.hpp" + +namespace sim::parts +{ + +extern sim::reactor::coolant::vessel* vessel; +extern sim::reactor::reactor<5, 5>* reactor; +extern sim::coolant::valve* valve; +extern sim::coolant::pump* pump; + +void init(); + +}; + diff --git a/src/reactor/coolant/vessel.cpp b/src/reactor/coolant/vessel.cpp index fa2bbf0..5cbd1ad 100644 --- a/src/reactor/coolant/vessel.cpp +++ b/src/reactor/coolant/vessel.cpp @@ -126,16 +126,3 @@ double vessel::get_pressure() const return (n * T * constants::R) / V; } -std::ostream& operator<<(std::ostream& o, const vessel& v) -{ - o << "Volume: " << v.get_volume() << " L\n"; - o << "Level: " << v.get_level() << " L\n"; - o << "Steam: " << v.get_steam() << " g\n"; - o << "Heat: " << v.get_heat() << " C\n"; - o << "Pressure: " << (v.get_pressure() * 0.001) << " kPa\n"; - o << "Void Ratio: " << (v.get_void_ratio() * 100) << " %\n"; - - return o; -} - - diff --git a/src/reactor/coolant/vessel.hpp b/src/reactor/coolant/vessel.hpp index b9dbc5a..f95a59d 100644 --- a/src/reactor/coolant/vessel.hpp +++ b/src/reactor/coolant/vessel.hpp @@ -41,9 +41,20 @@ public: constexpr double get_void_ratio() const { double s = steam_suspended / get_steam_density(); return s / (level + s); } double get_pressure() const; // pascals + + friend std::ostream& operator<<(std::ostream& o, const vessel& v) + { + o << "Volume: " << v.get_volume() << " L\n"; + o << "Level: " << v.get_level() << " L\n"; + o << "Steam: " << v.get_steam() << " g\n"; + o << "Heat: " << v.get_heat() << " C\n"; + o << "Pressure: " << (v.get_pressure() * 0.001) << " kPa\n"; + o << "Void Ratio: " << (v.get_void_ratio() * 100) << " %\n"; + + return o; + } + }; } -std::ostream& operator<<(std::ostream& o, const sim::reactor::coolant::vessel& v); - From 6f4dba097e9d5fcd4905d5dccd9751f045cf06ce Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Fri, 26 Jan 2024 00:49:30 +1100 Subject: [PATCH 18/43] delete file --- src/main.cpp.old | 216 ----------------------------------------------- 1 file changed, 216 deletions(-) delete mode 100644 src/main.cpp.old diff --git a/src/main.cpp.old b/src/main.cpp.old deleted file mode 100644 index 498291c..0000000 --- a/src/main.cpp.old +++ /dev/null @@ -1,216 +0,0 @@ - -#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; -} - From ea4b41f564acb633823aa2eceaa496508e166eb9 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Fri, 26 Jan 2024 13:27:29 +1100 Subject: [PATCH 19/43] moved reactor and builder to their own files --- src/graphics/mesh/model.cpp | 4 +- src/parts.cpp | 21 +++--- src/parts.hpp | 2 +- src/reactor/builder.cpp | 43 +++++++++++++ src/reactor/builder.hpp | 38 +---------- src/reactor/reactor.cpp | 123 ++++++++++++++++++++++++++++++++++++ src/reactor/reactor.hpp | 122 +++++------------------------------ 7 files changed, 198 insertions(+), 155 deletions(-) create mode 100644 src/reactor/builder.cpp create mode 100644 src/reactor/reactor.cpp diff --git a/src/graphics/mesh/model.cpp b/src/graphics/mesh/model.cpp index 1735f56..067ed89 100644 --- a/src/graphics/mesh/model.cpp +++ b/src/graphics/mesh/model.cpp @@ -62,7 +62,7 @@ static unsigned int proc_texture(const proc_state& state, aiMaterial* mat, const static void proc_mesh(proc_state& state, glm::mat4 mat, aiMesh* mesh, const aiScene* scene) { aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; - unsigned int texid = proc_texture(state, material, scene); + unsigned int handle = proc_texture(state, material, scene); unsigned int offset = state.offset; for(unsigned int i = 0; i < mesh->mNumVertices; i++) @@ -71,7 +71,7 @@ static void proc_mesh(proc_state& state, glm::mat4 mat, aiMesh* mesh, const aiSc auto [x, y, z] = mesh->mVertices[i]; vertex.pos = glm::vec4(x, y, z, 1) * mat; - vertex.texid = texid; + vertex.texid = handle; if(mesh->HasNormals()) { diff --git a/src/parts.cpp b/src/parts.cpp index e8069fe..9860ed8 100644 --- a/src/parts.cpp +++ b/src/parts.cpp @@ -10,23 +10,26 @@ using namespace sim; reactor::coolant::vessel* parts::vessel; -reactor::reactor<5, 5>* parts::reactor; +reactor::reactor* parts::reactor; coolant::valve* parts::valve; coolant::pump* parts::pump; void parts::init() { + const char* layout[] = { + "#C#C#", + "CFCFC", + "#C#C#", + "CFCFC", + "#C#C#" + }; + vessel = new reactor::coolant::vessel(8, 10, 300, sim::coolant::WATER); - reactor = new reactor::reactor<5, 5>(sim::reactor::builder<5, 5>( + reactor = new sim::reactor::reactor(sim::reactor::builder(5, 5, reactor::fuel::fuel_rod(2000, 4000), reactor::control::control_rod(*vessel, 10000, 1), - reactor::coolant::pipe(*vessel), { - "#C#C#", - "CFCFC", - "#C#C#", - "CFCFC", - "#C#C#" - })); + reactor::coolant::pipe(*vessel), + layout)); valve = new coolant::valve(*vessel, 1, 500); pump = new coolant::pump(*vessel, 1e4, 15); diff --git a/src/parts.hpp b/src/parts.hpp index f52c5ad..674d341 100644 --- a/src/parts.hpp +++ b/src/parts.hpp @@ -10,7 +10,7 @@ namespace sim::parts { extern sim::reactor::coolant::vessel* vessel; -extern sim::reactor::reactor<5, 5>* reactor; +extern sim::reactor::reactor* reactor; extern sim::coolant::valve* valve; extern sim::coolant::pump* pump; diff --git a/src/reactor/builder.cpp b/src/reactor/builder.cpp new file mode 100644 index 0000000..58e9cf3 --- /dev/null +++ b/src/reactor/builder.cpp @@ -0,0 +1,43 @@ + +#include "builder.hpp" + +using namespace sim::reactor; + +sim::reactor::reactor sim::reactor::builder(const int W, const int H, fuel::fuel_rod fr, control::control_rod cr, coolant::pipe p, const char** lines) +{ + std::unique_ptr arr[W * H]; + + for(int y = 0; y < H; y++) + for(int x = 0; x < W; x++) + { + char c = lines[y][x]; + rod* r; + + switch(c) + { + case 'F': + r = new fuel::fuel_rod(fr); + break; + case 'C': + r = new control::control_rod(cr); + break; + case 'G': + r = new control::graphite_rod(); + break; + case 'H': + r = new coolant::heater(); + break; + case ' ': + r = new coolant::pipe(p); + break; + case '#': + r = new rod(); + break; + } + + arr[y * W + x] = std::unique_ptr(std::move(r)); + } + + return reactor(arr, W, H); +} + diff --git a/src/reactor/builder.hpp b/src/reactor/builder.hpp index 16f2c84..79ba7ba 100644 --- a/src/reactor/builder.hpp +++ b/src/reactor/builder.hpp @@ -12,43 +12,7 @@ namespace sim::reactor { -template -reactor builder(fuel::fuel_rod fr, control::control_rod cr, coolant::pipe p, std::array lines) -{ - std::array arr; - - for(int y = 0; y < H; y++) - for(int x = 0; x < W; x++) - { - char c = lines[y][x]; - rod* r; - - switch(c) - { - case 'F': - r = new fuel::fuel_rod(fr); - break; - case 'C': - r = new control::control_rod(cr); - break; - case 'G': - r = new control::graphite_rod(); - break; - case 'H': - r = new coolant::heater(); - break; - case ' ': - r = new coolant::pipe(p); - break; - case '#': - r = new rod(); - } - - arr[y * W + x] = r; - } - - return reactor(arr); -} +reactor builder(const int W, const int H, fuel::fuel_rod fr, control::control_rod cr, coolant::pipe p, const char** lines); }; diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp new file mode 100644 index 0000000..be7991e --- /dev/null +++ b/src/reactor/reactor.cpp @@ -0,0 +1,123 @@ + +#include "reactor.hpp" + +#include + +using namespace sim::reactor; + +reactor::reactor(std::unique_ptr* rods, int width, int height) : width(width), height(height), size(width * height) +{ + this->rods = new std::unique_ptr[width * height]; + + for(int i = 0; i < size; i++) + { + this->rods[i] = std::move(rods[i]); + } +} + +reactor::reactor(reactor&& o) : width(o.width), height(o.height), size(o.size) +{ + rods = o.rods; + cursor = o.cursor; + o.rods = nullptr; +} + +reactor::~reactor() +{ + if(rods != nullptr) + { + delete[] rods; + } +} + +void reactor::update(std::mt19937& rand, double secs) +{ + int rods_lookup[size]; + + for(int i = 0; i < size; i++) + { + rods_lookup[i] = i; + } + + for(int i = 0; i < size; i++) + { + rods[i]->update(secs); + } + + update_interactions(rand, rods_lookup, secs / 2); +} + +void reactor::update_selected(int v) +{ + for(int i = 0; i < size; i++) + { + rod* r = rods[i].get(); + + if(r->is_selected()) + { + r->update_rod_selected(v); + } + } +} + +int reactor::move_cursor(int d) +{ + for(int i = 0; i < size; i++) + { + cursor = (cursor + d) % size; + + if(cursor < 0) + { + cursor += size; + } + + if(rods[cursor]->should_select()) + { + return cursor; + } + } + + return 0; +} + +void reactor::toggle_selected() +{ + if(rods[cursor]->should_select()) + { + rods[cursor]->toggle_selected(); + } +} + +void reactor::update_tile(std::mt19937& rand, double secs, int i, int x, int y) +{ + int nb_lookup[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; + std::shuffle(nb_lookup, &nb_lookup[3], rand); + + for(int j = 0; j < 4; j++) + { + int xp = x + nb_lookup[j][0]; + int yp = y + nb_lookup[j][1]; + + if(xp >= 0 && yp >= 0 && xp < width && yp < height) + { + rods[i]->interact(rods[yp * width + xp].get(), secs / 2); + } + } +} + +void reactor::update_interactions(std::mt19937& rand, int* rods_lookup, double secs) +{ + std::shuffle(rods_lookup, &rods_lookup[size - 1], rand); + + for(int id = 0; id < size; id++) + { + int i = rods_lookup[id]; + int x = i % width; + int y = i / width; + + for(int j = 0; j < 4; j++) + { + update_tile(rand, secs, i, x, y); + } + } +} diff --git a/src/reactor/reactor.hpp b/src/reactor/reactor.hpp index 856fba4..aecd1ab 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -3,126 +3,36 @@ #include "rod.hpp" -#include #include -#include #include +#include +#include namespace sim::reactor { -template struct reactor { - constexpr const static int width = W; - constexpr const static int height = H; - constexpr const static int size = W*H; - - rod* rods[size]; + const int width; + const int height; + const int size; + std::unique_ptr* rods; int cursor = 0; - reactor(std::array rods) - { - for(int i = 0; i < size; i++) - { - this->rods[i] = rods[i]; - } - } - - void update(std::mt19937& rand, double secs) - { - int rods_lookup[size]; - - for(int i = 0; i < size; i++) - { - rods_lookup[i] = i; - } - - for(int i = 0; i < size; i++) - { - rods[i]->update(secs); - } - - update_interactions(rand, rods_lookup, secs / 2); - } - - void update_selected(int v) - { - for(int i = 0; i < size; i++) - { - rod* r = rods[i]; - - if(r->is_selected()) - { - r->update_rod_selected(v); - } - } - } - - int move_cursor(int d) - { - for(int i = 0; i < size; i++) - { - cursor = (cursor + d) % size; - - if(cursor < 0) - { - cursor += size; - } - - if(rods[cursor]->should_select()) - { - return cursor; - } - } - - return 0; - } - - void toggle_selected() - { - if(rods[cursor]->should_select()) - { - rods[cursor]->toggle_selected(); - } - } + reactor(std::unique_ptr* rods, int width, int height); + reactor(reactor&& r); + ~reactor(); + + void update(std::mt19937& rand, double secs); + void update_selected(int v); + int move_cursor(int d); + void toggle_selected(); private: - void update_tile(std::mt19937& rand, double secs, int i, int x, int y) - { - std::array nb_lookup[4] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; - std::shuffle(nb_lookup, &nb_lookup[3], rand); - - for(int j = 0; j < 4; j++) - { - int xp = x + nb_lookup[j][0]; - int yp = y + nb_lookup[j][1]; - - if(xp >= 0 && yp >= 0 && xp < width && yp < height) - { - rods[i]->interact(rods[yp * width + xp], secs / 2); - } - } - } - - void update_interactions(std::mt19937& rand, int* rods_lookup, double secs) - { - std::shuffle(rods_lookup, &rods_lookup[size - 1], rand); - - for(int id = 0; id < size; id++) - { - int i = rods_lookup[id]; - int x = i % width; - int y = i / width; - - for(int j = 0; j < 4; j++) - { - update_tile(rand, secs, i, x, y); - } - } - } + void update_tile(std::mt19937& rand, double secs, int i, int x, int y); + void update_interactions(std::mt19937& rand, int* rods_lookup, double secs); }; } From 6c685273eb4fd7103e1bcde39e2e2a731c5f3aa1 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sat, 27 Jan 2024 12:01:52 +1100 Subject: [PATCH 20/43] better error handling, better font rendering; reactor copy --- src/graphics/mesh/font.cpp | 7 +++-- src/graphics/mesh/font.hpp | 9 ------ src/graphics/mesh/mesh.hpp | 11 ++++++++ src/graphics/window.cpp | 42 +++++++++++++++------------- src/reactor/control/control_rod.hpp | 1 + src/reactor/control/graphite_rod.hpp | 1 + src/reactor/coolant/heater.hpp | 1 + src/reactor/coolant/pipe.hpp | 2 ++ src/reactor/fuel/fuel_rod.hpp | 1 + src/reactor/reactor.cpp | 10 +++++++ src/reactor/reactor.hpp | 1 + src/reactor/rod.hpp | 1 + 12 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/graphics/mesh/font.cpp b/src/graphics/mesh/font.cpp index 147d39e..b621267 100644 --- a/src/graphics/mesh/font.cpp +++ b/src/graphics/mesh/font.cpp @@ -8,6 +8,7 @@ #include #include +#include "mesh.hpp" #include "arrays.hpp" #include "font.hpp" @@ -89,7 +90,7 @@ void font::init() } } -void font::generate(mesh& m, const char* text, double size) +void mesh::load_text(const char* text, double size) { std::vector vertices; std::vector indices; @@ -135,7 +136,7 @@ void font::generate(mesh& m, const char* text, double size) x += ch.advance * size; } - m.set_vertices(&vertices[0], vertices.size(), GL_DYNAMIC_DRAW); - m.set_indices(&indices[0], indices.size(), GL_DYNAMIC_DRAW); + set_vertices(&vertices[0], vertices.size(), GL_DYNAMIC_DRAW); + set_indices(&indices[0], indices.size(), GL_DYNAMIC_DRAW); } diff --git a/src/graphics/mesh/font.hpp b/src/graphics/mesh/font.hpp index 7af4f77..52b6ee0 100644 --- a/src/graphics/mesh/font.hpp +++ b/src/graphics/mesh/font.hpp @@ -10,15 +10,6 @@ namespace sim::graphics::font { void init(); -void generate(mesh& m, const char* text, double size); - -template -void generate(mesh& m, const char* header, T* item, double size) -{ - std::stringstream ss; - ss << header << *item; - generate(m, ss.str().c_str(), size); -} }; diff --git a/src/graphics/mesh/mesh.hpp b/src/graphics/mesh/mesh.hpp index 0ff256e..863d9c1 100644 --- a/src/graphics/mesh/mesh.hpp +++ b/src/graphics/mesh/mesh.hpp @@ -4,7 +4,9 @@ #include #include "arrays.hpp" + #include +#include namespace sim::graphics { @@ -26,7 +28,16 @@ struct mesh void set_vertices(const arrays::vertex* data, size_t size, int mode); void set_indices(const unsigned int* data, size_t size, int mode); void load_model(std::string base, std::string path); + void load_text(const char* text, double size); void render(); + + template + void load_text(const char* header, T* item, double size) + { + std::stringstream ss; + ss << header << *item; + load_text(ss.str().c_str(), size); + } }; }; diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index c93fec6..0e94afa 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -56,7 +56,20 @@ void window::create() if(err != GLEW_OK) { - std::cout << "GLEW Init Failed: " << glewGetErrorString(err) << "\n"; + std::cerr << "GLEW Init Failed: " << glewGetErrorString(err) << "\n"; + close(); + return; + } + + if(!glGetTextureHandleARB || !glMakeTextureHandleResidentARB) + { + std::cerr << "Fatal: Bindless textures not supported\n"; + + if(!glGetTextureHandleARB) + std::cerr << " Missing: glGetTextureHandleARB\n"; + if(!glMakeTextureHandleResidentARB) + std::cerr << " Missing: glMakeTextureHandleResidentARB\n"; + close(); return; } @@ -130,14 +143,10 @@ void window::create() void window::loop() { - MeshText.bind(); - font::generate(MeshText, "Reactor Core\n\nTODO", 0.1); - MeshMon1.bind(); - font::generate(MeshMon1, "Reactor Vessel\n\n", parts::vessel, 0.1); - MeshMon2.bind(); - font::generate(MeshMon2, "Steam Valve\n\n", parts::valve, 0.1); - MeshMon3.bind(); - font::generate(MeshMon3, "Coolant Pump\n\n", parts::pump, 0.1); + MeshText.bind(); MeshText.load_text("Reactor Core\n\nTODO", 0.1); + MeshMon1.bind(); MeshMon1.load_text("Reactor Vessel\n\n", parts::vessel, 0.1); + MeshMon2.bind(); MeshMon2.load_text("Steam Valve\n\n", parts::valve, 0.1); + MeshMon3.bind(); MeshMon3.load_text("Coolant Pump\n\n", parts::pump, 0.1); glm::mat4 mat_projection = glm::perspective(glm::radians(80.0f), resize::get_aspect(), 0.01f, 20.f); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); @@ -145,16 +154,11 @@ void window::loop() glClearColor(0, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - MeshScene.bind(); - MeshScene.render(); - MeshText.bind(); - MeshText.render(); - MeshMon1.bind(); - MeshMon1.render(); - MeshMon2.bind(); - MeshMon2.render(); - MeshMon3.bind(); - MeshMon3.render(); + MeshScene.bind(); MeshScene.render(); + MeshText.bind(); MeshText.render(); + MeshMon1.bind(); MeshMon1.render(); + MeshMon2.bind(); MeshMon2.render(); + MeshMon3.bind(); MeshMon3.render(); glfwSwapBuffers(win); glfwPollEvents(); diff --git a/src/reactor/control/control_rod.hpp b/src/reactor/control/control_rod.hpp index 806a47d..655b74d 100644 --- a/src/reactor/control/control_rod.hpp +++ b/src/reactor/control/control_rod.hpp @@ -17,6 +17,7 @@ class control_rod : public sim::reactor::coolant::pipe virtual void display(std::ostream& o) const; virtual const char* get_name() const { return "Control Rod"; } + virtual rod* clone() const { return new control_rod(*this); }; public: diff --git a/src/reactor/control/graphite_rod.hpp b/src/reactor/control/graphite_rod.hpp index 202381c..698f742 100644 --- a/src/reactor/control/graphite_rod.hpp +++ b/src/reactor/control/graphite_rod.hpp @@ -13,6 +13,7 @@ class graphite_rod : public sim::reactor::rod virtual void display(std::ostream& o) const; virtual const char* get_name() const { return "Graphite Rod"; } + virtual rod* clone() const { return new graphite_rod(*this); }; virtual double get_k(val_t type) const; public: diff --git a/src/reactor/coolant/heater.hpp b/src/reactor/coolant/heater.hpp index 05cab44..f9fa1a4 100644 --- a/src/reactor/coolant/heater.hpp +++ b/src/reactor/coolant/heater.hpp @@ -14,6 +14,7 @@ class heater : public sim::reactor::rod virtual const char* get_name() const { return "Heater"; } virtual double get_k(val_t type) const { return 0.5; } + virtual rod* clone() const { return new heater(*this); }; public: diff --git a/src/reactor/coolant/pipe.hpp b/src/reactor/coolant/pipe.hpp index 1e877bd..b184bec 100644 --- a/src/reactor/coolant/pipe.hpp +++ b/src/reactor/coolant/pipe.hpp @@ -16,6 +16,8 @@ protected: virtual double get_k(sim::reactor::rod::val_t type) const; virtual const char* get_name() const { return "Coolant"; } + virtual rod* clone() const { return new pipe(*this); }; + void update_pipe(double secs); public: diff --git a/src/reactor/fuel/fuel_rod.hpp b/src/reactor/fuel/fuel_rod.hpp index 892cd55..68a6635 100644 --- a/src/reactor/fuel/fuel_rod.hpp +++ b/src/reactor/fuel/fuel_rod.hpp @@ -15,6 +15,7 @@ class fuel_rod : public sim::reactor::rod virtual void display(std::ostream& o) const; virtual const char* get_name() const { return "Fuel"; } + virtual rod* clone() const { return new fuel_rod(*this); }; public: diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index be7991e..5722292 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -22,6 +22,16 @@ reactor::reactor(reactor&& o) : width(o.width), height(o.height), size(o.size) o.rods = nullptr; } +reactor::reactor(const reactor& o) : width(o.width), height(o.height), size(o.size) +{ + rods = new std::unique_ptr[width * height]; + + for(int i = 0; i < size; i++) + { + this->rods[i] = std::unique_ptr(o.rods[i]->clone()); + } +} + reactor::~reactor() { if(rods != nullptr) diff --git a/src/reactor/reactor.hpp b/src/reactor/reactor.hpp index aecd1ab..f34d917 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -21,6 +21,7 @@ struct reactor int cursor = 0; reactor(std::unique_ptr* rods, int width, int height); + reactor(const reactor& r); reactor(reactor&& r); ~reactor(); diff --git a/src/reactor/rod.hpp b/src/reactor/rod.hpp index eac3b50..685cb86 100644 --- a/src/reactor/rod.hpp +++ b/src/reactor/rod.hpp @@ -24,6 +24,7 @@ public: virtual void add(val_t type, double v); virtual double extract(val_t type, double s, double k, double o); virtual double get(val_t type) const; + virtual rod* clone() const { return new rod(*this); }; virtual bool should_display() const { return false; } virtual bool should_select() const { return false; } From 6b9cd89b3c11cd7a896d54fc9bca45910435a5d8 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sun, 28 Jan 2024 00:02:54 +1100 Subject: [PATCH 21/43] did things --- CMakeLists.txt | 2 +- src/graphics/locations.cpp | 30 ++++++++++++ src/graphics/locations.hpp | 12 +++++ src/graphics/mesh/font.cpp | 1 + src/graphics/mesh/mesh.hpp | 5 +- src/graphics/mesh/model.cpp | 2 +- src/graphics/mesh/texture.cpp | 9 +++- src/graphics/mesh/texture.hpp | 3 ++ src/graphics/monitor/core.cpp | 44 +++++++++++++++++ src/graphics/monitor/core.hpp | 23 +++++++++ src/graphics/monitor/vessel.cpp | 60 +++++++++++++++++++++++ src/graphics/monitor/vessel.hpp | 23 +++++++++ src/graphics/resize.cpp | 1 + src/graphics/shader.cpp | 80 ++++++++++--------------------- src/graphics/window.cpp | 84 ++++++++++----------------------- src/graphics/window.hpp | 4 +- src/main.cpp | 15 ++---- src/parts.cpp | 37 --------------- src/parts.hpp | 20 -------- src/random.cpp | 13 +++++ src/random.hpp | 14 ++++++ src/reactor/reactor.cpp | 15 +++--- src/reactor/reactor.hpp | 7 ++- src/system.cpp | 47 ++++++++++++++++++ src/system.hpp | 24 ++++++++++ 25 files changed, 374 insertions(+), 201 deletions(-) create mode 100644 src/graphics/locations.cpp create mode 100644 src/graphics/locations.hpp create mode 100644 src/graphics/monitor/core.cpp create mode 100644 src/graphics/monitor/core.hpp create mode 100644 src/graphics/monitor/vessel.cpp create mode 100644 src/graphics/monitor/vessel.hpp delete mode 100644 src/parts.cpp delete mode 100644 src/parts.hpp create mode 100644 src/random.cpp create mode 100644 src/random.hpp create mode 100644 src/system.cpp create mode 100644 src/system.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2649105..a7fc048 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.25) project(FastNuclearSim VERSION 1.0) set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_FLAGS "-g -lncurses -I/usr/include/freetype2") +set(CMAKE_CXX_FLAGS "-O3 -lncurses -I/usr/include/freetype2") file(GLOB_RECURSE SOURCES src/*.cpp) diff --git a/src/graphics/locations.cpp b/src/graphics/locations.cpp new file mode 100644 index 0000000..a6b812a --- /dev/null +++ b/src/graphics/locations.cpp @@ -0,0 +1,30 @@ + +#include "locations.hpp" +#include + +using namespace sim::graphics; + +const glm::mat4 locations::monitors[4] = { + ( + glm::translate(glm::mat4(1), glm::vec3(-2.949, -1.7778 + 0.05, 3 - 0.05)) * + glm::rotate(glm::mat4(1), glm::radians(-90), glm::vec3(1, 0, 0)) * + glm::rotate(glm::mat4(1), glm::radians(-90), glm::vec3(0, 1, 0)) * + glm::scale(glm::mat4(1), glm::vec3(1.9, 1.9, 1.9)) + ), + ( + glm::translate(glm::mat4(1), glm::vec3(-1.5 + 0.05, 3.949, 3 - 0.05)) * + glm::rotate(glm::mat4(1), glm::radians(-90), glm::vec3(1, 0, 0)) * + glm::scale(glm::mat4(1), glm::vec3(1.9, 1.9, 1.9)) + ), + ( + glm::translate(glm::mat4(1), glm::vec3(1 + 0.05, 3.949, 3 - 0.05)) * + glm::rotate(glm::mat4(1), glm::radians(-90), glm::vec3(1, 0, 0)) * + glm::scale(glm::mat4(1), glm::vec3(1.9, 1.9, 1.9)) + ), + ( + glm::translate(glm::mat4(1), glm::vec3(3.5 + 0.05, 3.949, 3 - 0.05)) * + glm::rotate(glm::mat4(1), glm::radians(-90), glm::vec3(1, 0, 0)) * + glm::scale(glm::mat4(1), glm::vec3(1.9, 1.9, 1.9)) + ) +}; + diff --git a/src/graphics/locations.hpp b/src/graphics/locations.hpp new file mode 100644 index 0000000..26b1a7c --- /dev/null +++ b/src/graphics/locations.hpp @@ -0,0 +1,12 @@ + +#pragma once + +#include + +namespace sim::graphics::locations +{ + +extern const glm::mat4 monitors[4]; + +}; + diff --git a/src/graphics/mesh/font.cpp b/src/graphics/mesh/font.cpp index b621267..eebc48b 100644 --- a/src/graphics/mesh/font.cpp +++ b/src/graphics/mesh/font.cpp @@ -1,6 +1,7 @@ #include #include + #include #include FT_FREETYPE_H diff --git a/src/graphics/mesh/mesh.hpp b/src/graphics/mesh/mesh.hpp index 863d9c1..dc2aa7c 100644 --- a/src/graphics/mesh/mesh.hpp +++ b/src/graphics/mesh/mesh.hpp @@ -28,14 +28,15 @@ struct mesh void set_vertices(const arrays::vertex* data, size_t size, int mode); void set_indices(const unsigned int* data, size_t size, int mode); void load_model(std::string base, std::string path); + void load_model(std::string path) { load_model(".", path); } void load_text(const char* text, double size); void render(); template - void load_text(const char* header, T* item, double size) + void load_text(const char* header, T& item, double size) { std::stringstream ss; - ss << header << *item; + ss << header << item; load_text(ss.str().c_str(), size); } }; diff --git a/src/graphics/mesh/model.cpp b/src/graphics/mesh/model.cpp index 067ed89..f9500a2 100644 --- a/src/graphics/mesh/model.cpp +++ b/src/graphics/mesh/model.cpp @@ -56,7 +56,7 @@ static unsigned int proc_texture(const proc_state& state, aiMaterial* mat, const return texture::load(state.base + "/" + filename); } - return 0; + return texture::handle_white; } static void proc_mesh(proc_state& state, glm::mat4 mat, aiMesh* mesh, const aiScene* scene) diff --git a/src/graphics/mesh/texture.cpp b/src/graphics/mesh/texture.cpp index 5f933c8..77cd633 100644 --- a/src/graphics/mesh/texture.cpp +++ b/src/graphics/mesh/texture.cpp @@ -11,6 +11,13 @@ using namespace sim::graphics; static std::unordered_map loaded; +unsigned int texture::handle_white; + +void texture::init() +{ + unsigned char pixels[] = {255, 255, 255, 255}; + handle_white = load_mem(pixels, 1, 1, 4); +} unsigned int texture::load_mem(const unsigned char* data, int width, int height, int channels) { @@ -43,7 +50,7 @@ unsigned int texture::load_mem(const unsigned char* data, int width, int height, unsigned int texid; glCreateTextures(GL_TEXTURE_2D, 1, &texid); - glTextureStorage2D(texid, 8, format_in, width, height); + glTextureStorage2D(texid, 1, format_in, width, height); glTextureSubImage2D(texid, 0, 0, 0, width, height, format, GL_UNSIGNED_BYTE, data); glTextureParameteri(texid, GL_TEXTURE_WRAP_S, GL_REPEAT); diff --git a/src/graphics/mesh/texture.hpp b/src/graphics/mesh/texture.hpp index d8b402d..3313fea 100644 --- a/src/graphics/mesh/texture.hpp +++ b/src/graphics/mesh/texture.hpp @@ -6,6 +6,9 @@ namespace sim::graphics::texture { +extern unsigned int handle_white; + +void init(); unsigned int load(std::string path); unsigned int load_mem(const unsigned char* data, int width, int height, int channels); unsigned int load_mem(const unsigned char* data, size_t len); diff --git a/src/graphics/monitor/core.cpp b/src/graphics/monitor/core.cpp new file mode 100644 index 0000000..81d53fd --- /dev/null +++ b/src/graphics/monitor/core.cpp @@ -0,0 +1,44 @@ + +#include "core.hpp" +#include "../locations.hpp" + +#include +#include + +using namespace sim::graphics::monitor; + +core::core() +{ + +} + +void core::init() +{ + mesh1.model_matrix = mesh2.model_matrix = locations::monitors[2]; + mesh1.colour_matrix = { + 1, 1, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + std::stringstream ss; + + ss << "Reactor Core"; + + mesh1.bind(); + mesh1.load_text(ss.str().c_str(), 0.05); + mesh2.bind(); + mesh2.load_model("../assets/model/", "reactor_core_interface.stl"); +} + +void core::update(sim::system& sys) +{ +} + +void core::render() +{ + mesh1.bind(); mesh1.render(); + mesh2.bind(); mesh2.render(); +} + diff --git a/src/graphics/monitor/core.hpp b/src/graphics/monitor/core.hpp new file mode 100644 index 0000000..1142ee0 --- /dev/null +++ b/src/graphics/monitor/core.hpp @@ -0,0 +1,23 @@ + +#pragma once + +#include "../mesh/mesh.hpp" +#include "../../system.hpp" + +namespace sim::graphics::monitor +{ + +class core +{ + sim::graphics::mesh mesh1, mesh2; + +public: + + core(); + void init(); + void update(sim::system& sys); + void render(); +}; + +}; + diff --git a/src/graphics/monitor/vessel.cpp b/src/graphics/monitor/vessel.cpp new file mode 100644 index 0000000..5dded40 --- /dev/null +++ b/src/graphics/monitor/vessel.cpp @@ -0,0 +1,60 @@ + +#include "vessel.hpp" +#include "../locations.hpp" + +#include +#include + +using namespace sim::graphics::monitor; + +vessel::vessel() +{ + +} + +void vessel::init() +{ + mesh1.model_matrix = locations::monitors[1]; + mesh2.model_matrix = glm::translate(mesh1.model_matrix, glm::vec3(3.0 / 8.0, 0, 0)); + + mesh1.colour_matrix = mesh2.colour_matrix = { + 1, 1, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + std::stringstream ss; + + ss << "Reactor Vessel\n\n"; + ss << "Heat\n"; + ss << "Steam\n"; + ss << "Pressure\n"; + ss << "Level\n"; + ss << "Void Ratio\n"; + + mesh1.bind(); + mesh1.load_text(ss.str().c_str(), 0.05); +} + +void vessel::update(sim::system& sys) +{ + std::stringstream ss; + + ss << "\n\n"; + ss << sys.vessel.get_heat() << " C\n"; + ss << sys.vessel.get_steam() << " g\n"; + ss << (sys.vessel.get_pressure() * 0.001) << " kPa\n"; + ss << sys.vessel.get_level() << " / " << sys.vessel.get_volume() << " L\n"; + ss << (sys.vessel.get_void_ratio() * 100) << " %\n"; + + mesh2.bind(); + mesh2.load_text(ss.str().c_str(), 0.05); +} + +void vessel::render() +{ + mesh1.bind(); mesh1.render(); + mesh2.bind(); mesh2.render(); +} + diff --git a/src/graphics/monitor/vessel.hpp b/src/graphics/monitor/vessel.hpp new file mode 100644 index 0000000..b173e77 --- /dev/null +++ b/src/graphics/monitor/vessel.hpp @@ -0,0 +1,23 @@ + +#pragma once + +#include "../mesh/mesh.hpp" +#include "../../system.hpp" + +namespace sim::graphics::monitor +{ + +class vessel +{ + sim::graphics::mesh mesh1, mesh2; + +public: + + vessel(); + void init(); + void update(sim::system& sys); + void render(); +}; + +}; + diff --git a/src/graphics/resize.cpp b/src/graphics/resize.cpp index cf562c0..c2cead9 100644 --- a/src/graphics/resize.cpp +++ b/src/graphics/resize.cpp @@ -53,6 +53,7 @@ static void cb_framebuffer_size(GLFWwindow* win, int w, int h) { win_w = w; win_h = h; + glViewport(0, 0, w, h); } diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp index f893e93..e02cdd4 100644 --- a/src/graphics/shader.cpp +++ b/src/graphics/shader.cpp @@ -3,85 +3,53 @@ #include #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 vec4 aPos; -layout (location = 3) in vec3 aNormal; - -uniform mat4 model; -uniform mat4 projection; - -out float brightness; -out flat sampler2D tex; -out vec2 texPos; - -void main() -{ - vec4 pos = model * aPos; - vec3 cNormal = vec3(0.f, 0.f, 1.f) * mat3(model); - - brightness = dot(normalize(aNormal), normalize(cNormal)) * 0.25f + 0.75f; - - gl_Position = projection * pos; - texPos = aTexPos; - tex = aTex; -} - -)"; - -static const char* FRAGMENT_SHADER = R"( -#version 460 core -#extension GL_ARB_bindless_texture : require - -in float brightness; -in flat sampler2D tex; -in vec2 texPos; - -out vec4 FragColour; - -uniform mat4 tex_mat; - -void main() -{ - vec4 texdata = texture2D(tex, texPos); - FragColour = tex_mat * texdata * vec4(vec3(brightness), 1); - - if(FragColour.a == 0) discard; -} - -)"; - static unsigned int prog_id; int shader::gl_tex_mat; int shader::gl_model; int shader::gl_projection; -static int load_shader(const char** src, int type) +static int load_shader(const char* src, int type) { int id = glCreateShader(type); - glShaderSource(id, 1, src, nullptr); + glShaderSource(id, 1, &src, nullptr); glCompileShader(id); return id; } +static std::string read_shader(const char* path) +{ + std::stringstream ss; + std::ifstream file(path, std::ios::binary); + char buff[1024]; + + while(!file.eof()) + { + file.read(buff, 1024); + ss.write(buff, file.gcount()); + } + + return ss.str(); +} + unsigned int shader::init_program() { + std::string shader_vsh = read_shader("../assets/shader/main.vsh"); + std::string shader_fsh = read_shader("../assets/shader/main.fsh"); + int success; - int vsh_id = load_shader(&VERTEX_SHADER, GL_VERTEX_SHADER); - int fsh_id = load_shader(&FRAGMENT_SHADER, GL_FRAGMENT_SHADER); + int vsh_id = load_shader(shader_vsh.c_str(), GL_VERTEX_SHADER); + int fsh_id = load_shader(shader_fsh.c_str(), GL_FRAGMENT_SHADER); prog_id = glCreateProgram(); glAttachShader(prog_id, vsh_id); diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 0e94afa..fc3f503 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -17,15 +17,20 @@ #include "window.hpp" #include "shader.hpp" #include "mesh/font.hpp" -#include "../parts.hpp" +#include "locations.hpp" +#include "../system.hpp" +#include "monitor/vessel.hpp" +#include "monitor/core.hpp" +#include "mesh/texture.hpp" using namespace sim::graphics; static GLFWwindow* win; static bool win_should_close = false; -static mesh MeshScene, MeshText; -static mesh MeshMon1, MeshMon2, MeshMon3; +static mesh MeshScene; +static monitor::vessel MonitorVessel; +static monitor::core MonitorCore; void GLAPIENTRY cb_debug_message(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { @@ -42,18 +47,19 @@ void window::create() glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); - + glfwWindowHint(GLFW_VISIBLE, false); + #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true); #endif - + win = glfwCreateWindow(800, 600, "FastNuclearSim", nullptr, nullptr); glfwMakeContextCurrent(win); glfwSwapInterval(1); - + GLenum err = glewInit(); - + if(err != GLEW_OK) { std::cerr << "GLEW Init Failed: " << glewGetErrorString(err) << "\n"; @@ -86,6 +92,7 @@ void window::create() keyboard::init(); mouse::init(); resize::init(); + texture::init(); font::init(); shader::init_program(); @@ -93,60 +100,17 @@ void window::create() MeshScene.bind(); MeshScene.load_model("../assets", "scene-baked.glb"); - glm::mat4 mat = glm::mat4(1); - mat = glm::translate(mat, glm::vec3(-2.949, -1.7778 + 0.05, 3 - 0.05)); - mat = glm::rotate(mat, glm::radians(-90), glm::vec3(1, 0, 0)); - mat = glm::rotate(mat, glm::radians(-90), glm::vec3(0, 1, 0)); - - MeshText.model_matrix = mat; - MeshText.colour_matrix = { - 1, 1, 1, 1, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0 - }; - - mat = glm::mat4(1); - mat = glm::translate(mat, glm::vec3(-1.5 + 0.05, 3.949, 3 - 0.05)); - mat = glm::rotate(mat, glm::radians(-90), glm::vec3(1, 0, 0)); - - MeshMon1.model_matrix = mat; - MeshMon1.colour_matrix = { - 1, 0.5, 0.5, 1, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0 - }; - - mat = glm::translate(glm::mat4(1), glm::vec3(2.5, 0, 0)) * mat; - - MeshMon2.model_matrix = mat; - MeshMon2.colour_matrix = { - 0.5, 1, 0.5, 1, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0 - }; - - mat = glm::translate(glm::mat4(1), glm::vec3(2.5, 0, 0)) * mat; - - MeshMon3.model_matrix = mat; - MeshMon3.colour_matrix = { - 0.5, 0.5, 1, 1, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0 - }; + MonitorCore.init(); + MonitorVessel.init(); + glfwShowWindow(win); glViewport(0, 0, 800, 600); } -void window::loop() +void window::loop(sim::system& sys) { - MeshText.bind(); MeshText.load_text("Reactor Core\n\nTODO", 0.1); - MeshMon1.bind(); MeshMon1.load_text("Reactor Vessel\n\n", parts::vessel, 0.1); - MeshMon2.bind(); MeshMon2.load_text("Steam Valve\n\n", parts::valve, 0.1); - MeshMon3.bind(); MeshMon3.load_text("Coolant Pump\n\n", parts::pump, 0.1); + MonitorCore.update(sys); + MonitorVessel.update(sys); glm::mat4 mat_projection = glm::perspective(glm::radians(80.0f), resize::get_aspect(), 0.01f, 20.f); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); @@ -155,10 +119,9 @@ void window::loop() glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); MeshScene.bind(); MeshScene.render(); - MeshText.bind(); MeshText.render(); - MeshMon1.bind(); MeshMon1.render(); - MeshMon2.bind(); MeshMon2.render(); - MeshMon3.bind(); MeshMon3.render(); + + MonitorCore.render(); + MonitorVessel.render(); glfwSwapBuffers(win); glfwPollEvents(); @@ -176,6 +139,7 @@ void window::close() void window::destroy() { + glfwDestroyWindow(win); glfwTerminate(); } diff --git a/src/graphics/window.hpp b/src/graphics/window.hpp index 3b031fd..52e2368 100644 --- a/src/graphics/window.hpp +++ b/src/graphics/window.hpp @@ -3,12 +3,14 @@ #include +#include "../system.hpp" + namespace sim::graphics::window { void create(); bool should_close(); -void loop(); +void loop(sim::system& sys); void destroy(); void close(); diff --git a/src/main.cpp b/src/main.cpp index f1466fd..79e1d88 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ #include "graphics/window.hpp" #include "graphics/camera.hpp" -#include "parts.hpp" +#include "system.hpp" using namespace sim; @@ -26,11 +26,8 @@ unsigned long get_now() int main() { - std::random_device rd; - std::mt19937 rand(rd()); - - parts::init(); graphics::window::create(); + sim::system sys(system::generate()); long clock = get_now(); @@ -41,13 +38,9 @@ int main() double dt = (double)passed / 1e6; clock += passed; - parts::reactor->update(rand, dt); - parts::pump->update(dt); - parts::valve->update(dt); - parts::vessel->update(dt); - + sys.update(dt); graphics::camera::update(dt); - graphics::window::loop(); + graphics::window::loop(sys); } graphics::window::destroy(); diff --git a/src/parts.cpp b/src/parts.cpp deleted file mode 100644 index 9860ed8..0000000 --- a/src/parts.cpp +++ /dev/null @@ -1,37 +0,0 @@ - -#include "parts.hpp" - -#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" - -using namespace sim; - -reactor::coolant::vessel* parts::vessel; -reactor::reactor* parts::reactor; -coolant::valve* parts::valve; -coolant::pump* parts::pump; - -void parts::init() -{ - const char* layout[] = { - "#C#C#", - "CFCFC", - "#C#C#", - "CFCFC", - "#C#C#" - }; - - vessel = new reactor::coolant::vessel(8, 10, 300, sim::coolant::WATER); - reactor = new sim::reactor::reactor(sim::reactor::builder(5, 5, - reactor::fuel::fuel_rod(2000, 4000), - reactor::control::control_rod(*vessel, 10000, 1), - reactor::coolant::pipe(*vessel), - layout)); - - valve = new coolant::valve(*vessel, 1, 500); - pump = new coolant::pump(*vessel, 1e4, 15); -} - diff --git a/src/parts.hpp b/src/parts.hpp deleted file mode 100644 index 674d341..0000000 --- a/src/parts.hpp +++ /dev/null @@ -1,20 +0,0 @@ - -#pragma once - -#include "reactor/coolant/vessel.hpp" -#include "reactor/reactor.hpp" -#include "coolant/pump.hpp" -#include "coolant/valve.hpp" - -namespace sim::parts -{ - -extern sim::reactor::coolant::vessel* vessel; -extern sim::reactor::reactor* reactor; -extern sim::coolant::valve* valve; -extern sim::coolant::pump* pump; - -void init(); - -}; - diff --git a/src/random.cpp b/src/random.cpp new file mode 100644 index 0000000..f57f5e3 --- /dev/null +++ b/src/random.cpp @@ -0,0 +1,13 @@ + +#include "random.hpp" + +using namespace sim; + +std::mt19937 random::gen; + +void random::init() +{ + std::random_device rd; + gen = std::mt19937(rd()); +} + diff --git a/src/random.hpp b/src/random.hpp new file mode 100644 index 0000000..0b97b0a --- /dev/null +++ b/src/random.hpp @@ -0,0 +1,14 @@ + +#pragma once + +#include + +namespace sim::random +{ + +extern std::mt19937 gen; + +void init(); + +}; + diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index 5722292..32ee09d 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -1,5 +1,6 @@ #include "reactor.hpp" +#include "../random.hpp" #include @@ -40,7 +41,7 @@ reactor::~reactor() } } -void reactor::update(std::mt19937& rand, double secs) +void reactor::update(double secs) { int rods_lookup[size]; @@ -54,7 +55,7 @@ void reactor::update(std::mt19937& rand, double secs) rods[i]->update(secs); } - update_interactions(rand, rods_lookup, secs / 2); + update_interactions(rods_lookup, secs / 2); } void reactor::update_selected(int v) @@ -98,10 +99,10 @@ void reactor::toggle_selected() } } -void reactor::update_tile(std::mt19937& rand, double secs, int i, int x, int y) +void reactor::update_tile(double secs, int i, int x, int y) { int nb_lookup[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; - std::shuffle(nb_lookup, &nb_lookup[3], rand); + std::shuffle(nb_lookup, &nb_lookup[3], sim::random::gen); for(int j = 0; j < 4; j++) { @@ -115,9 +116,9 @@ void reactor::update_tile(std::mt19937& rand, double secs, int i, int x, int y) } } -void reactor::update_interactions(std::mt19937& rand, int* rods_lookup, double secs) +void reactor::update_interactions(int* rods_lookup, double secs) { - std::shuffle(rods_lookup, &rods_lookup[size - 1], rand); + std::shuffle(rods_lookup, &rods_lookup[size - 1], sim::random::gen); for(int id = 0; id < size; id++) { @@ -127,7 +128,7 @@ void reactor::update_interactions(std::mt19937& rand, int* rods_lookup, double s for(int j = 0; j < 4; j++) { - update_tile(rand, secs, i, x, y); + update_tile(secs, i, x, y); } } } diff --git a/src/reactor/reactor.hpp b/src/reactor/reactor.hpp index f34d917..0a64139 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -3,7 +3,6 @@ #include "rod.hpp" -#include #include #include #include @@ -25,15 +24,15 @@ struct reactor reactor(reactor&& r); ~reactor(); - void update(std::mt19937& rand, double secs); + void update(double secs); void update_selected(int v); int move_cursor(int d); void toggle_selected(); private: - void update_tile(std::mt19937& rand, double secs, int i, int x, int y); - void update_interactions(std::mt19937& rand, int* rods_lookup, double secs); + void update_tile(double secs, int i, int x, int y); + void update_interactions(int* rods_lookup, double secs); }; } diff --git a/src/system.cpp b/src/system.cpp new file mode 100644 index 0000000..748f4ad --- /dev/null +++ b/src/system.cpp @@ -0,0 +1,47 @@ + +#include "system.hpp" + +#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" + +using namespace sim; + +sim::system sim::system::generate() +{ + const char* layout[] = { + "#C#C#", + "CFCFC", + "#C#C#", + "CFCFC", + "#C#C#" + }; + + reactor::coolant::vessel vessel(8, 10, 300, sim::coolant::WATER); + sim::reactor::reactor reactor(sim::reactor::builder(5, 5, + reactor::fuel::fuel_rod(2000, 4000), + reactor::control::control_rod(vessel, 10000, 1), + reactor::coolant::pipe(vessel), + layout)); + + coolant::valve valve(vessel, 1, 500); + coolant::pump pump(vessel, 1e4, 15); + + return { + .reactor = reactor, + .vessel = vessel, + .valve = valve, + .pump = pump + }; +} + +void system::update(double dt) +{ + vessel.update(dt); + reactor.update(dt); + valve.update(dt); + pump.update(dt); +} + diff --git a/src/system.hpp b/src/system.hpp new file mode 100644 index 0000000..b52d872 --- /dev/null +++ b/src/system.hpp @@ -0,0 +1,24 @@ + +#pragma once + +#include "reactor/coolant/vessel.hpp" +#include "reactor/reactor.hpp" +#include "coolant/pump.hpp" +#include "coolant/valve.hpp" + +namespace sim +{ + +struct system +{ + sim::reactor::reactor reactor; + sim::reactor::coolant::vessel vessel; + sim::coolant::valve valve; + sim::coolant::pump pump; + + void update(double dt); + static system generate(); +}; + +}; + From 2e0a949ab8ea46240ff448ce931fcfa88a3ffbc6 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Mon, 29 Jan 2024 01:26:07 +1100 Subject: [PATCH 22/43] made sim more accurate, fancy displays now show stuff --- src/graphics/mesh/font.cpp | 4 +- src/graphics/mesh/glmesh.cpp | 78 ++++++++++++++++ src/graphics/mesh/glmesh.hpp | 34 +++++++ src/graphics/mesh/mesh.cpp | 89 +++++++------------ src/graphics/mesh/mesh.hpp | 23 ++--- src/graphics/mesh/model.cpp | 7 +- src/graphics/monitor/core.cpp | 63 ++++++++++--- src/graphics/monitor/core.hpp | 7 +- src/graphics/monitor/vessel.cpp | 79 +++++++++++++--- src/graphics/monitor/vessel.hpp | 4 +- src/graphics/window.cpp | 13 +-- src/main.cpp | 2 +- src/reactor/builder.cpp | 17 ++-- src/reactor/builder.hpp | 5 +- src/reactor/control/boron_rod.cpp | 61 +++++++++++++ .../{control_rod.hpp => boron_rod.hpp} | 13 +-- src/reactor/control/control_rod.cpp | 47 ---------- src/reactor/control/graphite_rod.cpp | 6 ++ src/reactor/control/graphite_rod.hpp | 5 +- src/reactor/coolant/heater.hpp | 2 + src/reactor/coolant/pipe.cpp | 6 +- src/reactor/coolant/pipe.hpp | 1 + src/reactor/coolant/vessel.cpp | 6 +- src/reactor/coolant/vessel.hpp | 4 +- src/reactor/fuel/fuel_rod.cpp | 70 +++++++++++++-- src/reactor/fuel/fuel_rod.hpp | 6 +- src/reactor/fuel/sample.cpp | 10 +-- src/reactor/fuel/sample.hpp | 2 +- src/reactor/reactor.cpp | 39 +++++++- src/reactor/reactor.hpp | 7 +- src/reactor/rod.cpp | 7 ++ src/reactor/rod.hpp | 12 ++- src/system.cpp | 78 ++++++++++------ src/system.hpp | 16 ++-- 34 files changed, 588 insertions(+), 235 deletions(-) create mode 100644 src/graphics/mesh/glmesh.cpp create mode 100644 src/graphics/mesh/glmesh.hpp create mode 100644 src/reactor/control/boron_rod.cpp rename src/reactor/control/{control_rod.hpp => boron_rod.hpp} (54%) delete mode 100644 src/reactor/control/control_rod.cpp diff --git a/src/graphics/mesh/font.cpp b/src/graphics/mesh/font.cpp index eebc48b..846e68d 100644 --- a/src/graphics/mesh/font.cpp +++ b/src/graphics/mesh/font.cpp @@ -137,7 +137,7 @@ void mesh::load_text(const char* text, double size) x += ch.advance * size; } - set_vertices(&vertices[0], vertices.size(), GL_DYNAMIC_DRAW); - set_indices(&indices[0], indices.size(), GL_DYNAMIC_DRAW); + set_vertices(&vertices[0], vertices.size()); + set_indices(&indices[0], indices.size()); } diff --git a/src/graphics/mesh/glmesh.cpp b/src/graphics/mesh/glmesh.cpp new file mode 100644 index 0000000..9e9b47e --- /dev/null +++ b/src/graphics/mesh/glmesh.cpp @@ -0,0 +1,78 @@ + +#include +#include + +#include "glmesh.hpp" +#include "arrays.hpp" +#include "../shader.hpp" +#include "../camera.hpp" + +using namespace sim::graphics; + +constexpr static void init(glmesh* m) +{ + if(m->vao != 0) + { + return; + } + + glGenVertexArrays(1, &m->vao); + glGenBuffers(1, &m->vbo); + glGenBuffers(1, &m->ebo); + + glBindVertexArray(m->vao); + glBindBuffer(GL_ARRAY_BUFFER, m->vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo); + + arrays::vertex_attrib_pointers(); +} + +glmesh::glmesh(glmesh&& o) +{ + vbo = o.vbo; + ebo = o.ebo; + vao = o.vao; + size = o.size; + colour_matrix = o.colour_matrix; + model_matrix = o.model_matrix; + + o.vbo = 0; + o.ebo = 0; + o.vao = 0; +} + +glmesh::~glmesh() +{ + if(vbo) glDeleteBuffers(1, &vbo); + if(ebo) glDeleteBuffers(1, &ebo); + if(vao) glDeleteVertexArrays(1, &vao); +} + +void glmesh::bind() +{ + init(this); + + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); +} + +void glmesh::uniform() +{ + glm::mat4 m = camera::get_matrix() * model_matrix; + glUniformMatrix4fv(shader::gl_model, 1, false, &m[0][0]); + glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &colour_matrix[0][0]); +} + +void glmesh::set(const mesh& m, int mode) +{ + glBufferData(GL_ARRAY_BUFFER, m.vertices.size() * sizeof(m.vertices[0]), &m.vertices[0], mode); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, m.indices.size() * sizeof(m.indices[0]), &m.indices[0], mode); + this->size = m.indices.size(); +} + +void glmesh::render() +{ + glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0); +} + diff --git a/src/graphics/mesh/glmesh.hpp b/src/graphics/mesh/glmesh.hpp new file mode 100644 index 0000000..ff04e6d --- /dev/null +++ b/src/graphics/mesh/glmesh.hpp @@ -0,0 +1,34 @@ + +#pragma once + +#include + +#include "arrays.hpp" +#include "mesh.hpp" + +#include + +namespace sim::graphics +{ + +struct glmesh +{ + unsigned int vao = 0, vbo = 0, ebo = 0, size = 0; + + glm::mat4 model_matrix {1.0f}; + glm::mat4 colour_matrix {1.0f}; + + constexpr glmesh() { } + + glmesh(glmesh&& o); + glmesh(const glmesh& o) = delete; + ~glmesh(); + + void bind(); + void uniform(); + void set(const mesh& m, int mode); + void render(); +}; + +}; + diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp index 3a1ed76..a411602 100644 --- a/src/graphics/mesh/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -1,7 +1,4 @@ -#include -#include - #include "mesh.hpp" #include "arrays.hpp" #include "../shader.hpp" @@ -9,71 +6,47 @@ using namespace sim::graphics; -constexpr static void init(mesh* m) +void mesh::add(const mesh& o, glm::mat4 mat) { - if(m->vao != 0) + unsigned int off = vertices.size(); + glm::mat3 mat3(mat); + + vertices.reserve(vertices.size() + o.vertices.size()); + indices.reserve(indices.size() + o.indices.size()); + + for(unsigned int i = 0; i < o.vertices.size(); i++) { - return; + arrays::vertex v = o.vertices[i]; + v.normal = v.normal * mat3; + v.pos = v.pos * mat; + vertices.push_back(v); } - glGenVertexArrays(1, &m->vao); - glGenBuffers(1, &m->vbo); - glGenBuffers(1, &m->ebo); - - glBindVertexArray(m->vao); - glBindBuffer(GL_ARRAY_BUFFER, m->vbo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ebo); - - arrays::vertex_attrib_pointers(); + for(unsigned int i = 0; i < o.indices.size(); i++) + { + indices.push_back(o.indices[i] + off); + } } -mesh::mesh(mesh&& o) +void mesh::set_vertices(const arrays::vertex* data, size_t size) { - vbo = o.vbo; - ebo = o.ebo; - vao = o.vao; - size = o.size; - colour_matrix = o.colour_matrix; - model_matrix = o.model_matrix; + vertices.clear(); + vertices.reserve(size); - o.vbo = 0; - o.ebo = 0; - o.vao = 0; + for(unsigned int i = 0; i < size; i++) + { + vertices.push_back(data[i]); + } } -mesh::~mesh() +void mesh::set_indices(const unsigned int* data, size_t size) { - if(vbo) glDeleteBuffers(1, &vbo); - if(ebo) glDeleteBuffers(1, &ebo); - if(vao) glDeleteVertexArrays(1, &vao); -} - -void mesh::set_vertices(const arrays::vertex* data, size_t size, int mode) -{ - glBufferData(GL_ARRAY_BUFFER, size * sizeof(data[0]), data, mode); -} - -void mesh::set_indices(const unsigned int* data, size_t size, int mode) -{ - glBufferData(GL_ELEMENT_ARRAY_BUFFER, size * sizeof(data[0]), data, mode); - this->size = size; -} - -void mesh::bind() -{ - init(this); - - glm::mat4 m = camera::get_matrix() * model_matrix; - glUniformMatrix4fv(shader::gl_model, 1, false, &m[0][0]); - glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &colour_matrix[0][0]); - - glBindVertexArray(vao); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); -} - -void mesh::render() -{ - glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0); + indices.clear(); + indices.reserve(size); + + for(unsigned int i = 0; i < size; i++) + { + indices.push_back(data[i]); + } } diff --git a/src/graphics/mesh/mesh.hpp b/src/graphics/mesh/mesh.hpp index dc2aa7c..698cbc1 100644 --- a/src/graphics/mesh/mesh.hpp +++ b/src/graphics/mesh/mesh.hpp @@ -2,35 +2,30 @@ #pragma once #include +#include +#include + +#include #include "arrays.hpp" -#include -#include namespace sim::graphics { struct mesh { - unsigned int vao = 0, vbo = 0, ebo = 0, size = 0; - - glm::mat4 model_matrix {1.0f}; - glm::mat4 colour_matrix {1.0f}; + std::vector vertices; + std::vector indices; constexpr mesh() { } - mesh(mesh&& o); - mesh(const mesh& o) = delete; - ~mesh(); - - void bind(); - void set_vertices(const arrays::vertex* data, size_t size, int mode); - void set_indices(const unsigned int* data, size_t size, int mode); + void set_vertices(const arrays::vertex* data, size_t size); + void set_indices(const unsigned int* data, size_t size); void load_model(std::string base, std::string path); void load_model(std::string path) { load_model(".", path); } void load_text(const char* text, double size); - void render(); + void add(const mesh& o, glm::mat4 mat); template void load_text(const char* header, T& item, double size) diff --git a/src/graphics/mesh/model.cpp b/src/graphics/mesh/model.cpp index f9500a2..949944b 100644 --- a/src/graphics/mesh/model.cpp +++ b/src/graphics/mesh/model.cpp @@ -64,6 +64,7 @@ static void proc_mesh(proc_state& state, glm::mat4 mat, aiMesh* mesh, const aiSc aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; unsigned int handle = proc_texture(state, material, scene); unsigned int offset = state.offset; + glm::mat3 mat3(mat); for(unsigned int i = 0; i < mesh->mNumVertices; i++) { @@ -76,7 +77,7 @@ static void proc_mesh(proc_state& state, glm::mat4 mat, aiMesh* mesh, const aiSc if(mesh->HasNormals()) { auto [x, y, z] = mesh->mNormals[i]; - vertex.normal = glm::vec3(x, y, z) * glm::mat3(mat); + vertex.normal = glm::vec3(x, y, z) * mat3; } if(mesh->mTextureCoords[0]) @@ -165,7 +166,7 @@ void mesh::load_model(std::string base, std::string filename) proc_node(state, glm::mat4(1), scene->mRootNode, scene); - set_vertices(&state.vertices[0], state.vertices.size(), GL_STATIC_DRAW); - set_indices(&state.indices[0], state.indices.size(), GL_STATIC_DRAW); + set_vertices(&state.vertices[0], state.vertices.size()); + set_indices(&state.indices[0], state.indices.size()); } diff --git a/src/graphics/monitor/core.cpp b/src/graphics/monitor/core.cpp index 81d53fd..522760f 100644 --- a/src/graphics/monitor/core.cpp +++ b/src/graphics/monitor/core.cpp @@ -1,4 +1,7 @@ +#include +#include + #include "core.hpp" #include "../locations.hpp" @@ -14,7 +17,7 @@ core::core() void core::init() { - mesh1.model_matrix = mesh2.model_matrix = locations::monitors[2]; + mesh1.model_matrix = locations::monitors[2]; mesh1.colour_matrix = { 1, 1, 1, 1, 0, 0, 0, 0, @@ -22,23 +25,57 @@ void core::init() 0, 0, 0, 0 }; - std::stringstream ss; - - ss << "Reactor Core"; + sim::graphics::mesh rmesh1, rmesh2; + + rmesh1.load_text("Reactor Core", 0.04); + rmesh2.load_model("../assets/model/", "reactor_core_interface_circle.stl"); + rmesh1.add(rmesh2, glm::mat4(1)); mesh1.bind(); - mesh1.load_text(ss.str().c_str(), 0.05); + mesh1.set(rmesh1, GL_STATIC_DRAW); + rmesh2.load_model("../assets/model/", "reactor_core_interface_cell.stl"); mesh2.bind(); - mesh2.load_model("../assets/model/", "reactor_core_interface.stl"); + mesh2.set(rmesh2, GL_STATIC_DRAW); } -void core::update(sim::system& sys) +void core::render(sim::system& sys) { -} - -void core::render() -{ - mesh1.bind(); mesh1.render(); - mesh2.bind(); mesh2.render(); + double step = 1 / (sys.vessel->diameter / sys.reactor->cell_width * 0.8); + double sx = 0.5 - (sys.reactor->width - 1) * step / 2.0; + double sy = 0.5 - (sys.reactor->height - 1) * step / 2.0; + + glm::mat4 mat_scale = glm::scale(glm::mat4(1), glm::vec3(step * 0.5, step * 0.5, 1)); + + mesh1.bind(); + mesh1.uniform(); + mesh1.render(); + mesh2.bind(); + + for(int i = 0; i < sys.reactor->size; i++) + { + int x = i % sys.reactor->width; + int y = i / sys.reactor->width; + double ox = sx + x * step; + double oy = sy + y * step; + + reactor::rod* r = sys.reactor->rods[i].get(); + glm::vec4 colour = r->get_colour(); + + if(!r->should_display() || colour[3] == 0) + { + continue; + } + + mesh2.model_matrix = mesh1.model_matrix * glm::translate(glm::mat4(1), glm::vec3(ox, oy, 0)) * mat_scale; + mesh2.colour_matrix = glm::mat4({ + colour[0], colour[1], colour[2], colour[3], + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }); + + mesh2.uniform(); + mesh2.render(); + } } diff --git a/src/graphics/monitor/core.hpp b/src/graphics/monitor/core.hpp index 1142ee0..09908f2 100644 --- a/src/graphics/monitor/core.hpp +++ b/src/graphics/monitor/core.hpp @@ -1,7 +1,7 @@ #pragma once -#include "../mesh/mesh.hpp" +#include "../mesh/glmesh.hpp" #include "../../system.hpp" namespace sim::graphics::monitor @@ -9,14 +9,13 @@ namespace sim::graphics::monitor class core { - sim::graphics::mesh mesh1, mesh2; + sim::graphics::glmesh mesh1, mesh2; public: core(); void init(); - void update(sim::system& sys); - void render(); + void render(sim::system& sys); }; }; diff --git a/src/graphics/monitor/vessel.cpp b/src/graphics/monitor/vessel.cpp index 5dded40..8c985fe 100644 --- a/src/graphics/monitor/vessel.cpp +++ b/src/graphics/monitor/vessel.cpp @@ -1,5 +1,10 @@ +#include +#include + #include "vessel.hpp" +#include "../../reactor/rod.hpp" +#include "../../reactor/control/boron_rod.hpp" #include "../locations.hpp" #include @@ -15,7 +20,7 @@ vessel::vessel() void vessel::init() { mesh1.model_matrix = locations::monitors[1]; - mesh2.model_matrix = glm::translate(mesh1.model_matrix, glm::vec3(3.0 / 8.0, 0, 0)); + mesh2.model_matrix = glm::translate(mesh1.model_matrix, glm::vec3(0.5, 0, 0)); mesh1.colour_matrix = mesh2.colour_matrix = { 1, 1, 1, 1, @@ -25,36 +30,88 @@ void vessel::init() }; std::stringstream ss; + sim::graphics::mesh rmesh; ss << "Reactor Vessel\n\n"; ss << "Heat\n"; ss << "Steam\n"; ss << "Pressure\n"; ss << "Level\n"; - ss << "Void Ratio\n"; + ss << "Void Ratio\n\n\n\n"; + ss << "Reactor Core\n\n"; + ss << "Temperature\nMin\nMax\n\n"; + ss << "Neutron Flux\nSlow\nFast\n\n"; + ss << "Control Rods\nMin\nMax\n\n"; + rmesh.load_text(ss.str().c_str(), 0.04); mesh1.bind(); - mesh1.load_text(ss.str().c_str(), 0.05); + mesh1.set(rmesh, GL_STATIC_DRAW); +} + +static double show(double v) +{ + return std::round(v * 1e3) * 1e-3; } void vessel::update(sim::system& sys) { std::stringstream ss; + sim::graphics::mesh rmesh; + + double temp_min, temp_max; + double crod_min = INFINITY, crod_max = -INFINITY; + + sys.reactor->get_stats(sim::reactor::rod::val_t::HEAT, temp_min, temp_max); + + for(int i = 0; i < sys.reactor->size; i++) + { + sim::reactor::rod* r = sys.reactor->rods[i].get(); + + if(r->get_id() != 5) + { + continue; + } + + auto br = (sim::reactor::control::boron_rod*)r; + double v = br->get_inserted(); + + if(v > crod_max) + { + crod_max = v; + } + + if(v < crod_min) + { + crod_min = v; + } + } ss << "\n\n"; - ss << sys.vessel.get_heat() << " C\n"; - ss << sys.vessel.get_steam() << " g\n"; - ss << (sys.vessel.get_pressure() * 0.001) << " kPa\n"; - ss << sys.vessel.get_level() << " / " << sys.vessel.get_volume() << " L\n"; - ss << (sys.vessel.get_void_ratio() * 100) << " %\n"; + ss << show( sys.vessel->get_heat() ) << " C\n"; + ss << show( sys.vessel->get_steam() ) << " g\n"; + ss << show( sys.vessel->get_pressure() * 0.001 ) << " kPa\n"; + ss << show( sys.vessel->get_level() ) << " / " << show( sys.vessel->get_volume() ) << " L\n"; + ss << show( sys.vessel->get_void_ratio() * 100 ) << " %\n\n\n\n\n\n\n"; + ss << show( temp_min ) << " C\n"; + ss << show( temp_max ) << " C\n\n\n"; + ss << sys.reactor->get_total(sim::reactor::rod::val_t::N_SLOW) << " mol\n"; + ss << sys.reactor->get_total(sim::reactor::rod::val_t::N_FAST) << " mol\n\n\n"; + ss << show( crod_min * 100 ) << " %\n"; + ss << show( crod_max * 100 ) << " %\n"; + rmesh.load_text(ss.str().c_str(), 0.04); mesh2.bind(); - mesh2.load_text(ss.str().c_str(), 0.05); + mesh2.set(rmesh, GL_DYNAMIC_DRAW); } void vessel::render() { - mesh1.bind(); mesh1.render(); - mesh2.bind(); mesh2.render(); + mesh1.bind(); + mesh1.uniform(); + mesh1.render(); + + mesh2.bind(); + mesh2.uniform(); + mesh2.render(); } diff --git a/src/graphics/monitor/vessel.hpp b/src/graphics/monitor/vessel.hpp index b173e77..016bd08 100644 --- a/src/graphics/monitor/vessel.hpp +++ b/src/graphics/monitor/vessel.hpp @@ -1,7 +1,7 @@ #pragma once -#include "../mesh/mesh.hpp" +#include "../mesh/glmesh.hpp" #include "../../system.hpp" namespace sim::graphics::monitor @@ -9,7 +9,7 @@ namespace sim::graphics::monitor class vessel { - sim::graphics::mesh mesh1, mesh2; + sim::graphics::glmesh mesh1, mesh2; public: diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index fc3f503..b09996a 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -28,7 +28,7 @@ using namespace sim::graphics; static GLFWwindow* win; static bool win_should_close = false; -static mesh MeshScene; +static glmesh MeshScene; static monitor::vessel MonitorVessel; static monitor::core MonitorCore; @@ -96,9 +96,11 @@ void window::create() font::init(); shader::init_program(); + mesh rmesh; + rmesh.load_model("../assets", "scene-baked.glb"); MeshScene.bind(); - MeshScene.load_model("../assets", "scene-baked.glb"); + MeshScene.set(rmesh, GL_STATIC_DRAW); MonitorCore.init(); MonitorVessel.init(); @@ -109,7 +111,6 @@ void window::create() void window::loop(sim::system& sys) { - MonitorCore.update(sys); MonitorVessel.update(sys); glm::mat4 mat_projection = glm::perspective(glm::radians(80.0f), resize::get_aspect(), 0.01f, 20.f); @@ -118,9 +119,11 @@ void window::loop(sim::system& sys) glClearColor(0, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - MeshScene.bind(); MeshScene.render(); + MeshScene.bind(); + MeshScene.uniform(); + MeshScene.render(); - MonitorCore.render(); + MonitorCore.render(sys); MonitorVessel.render(); glfwSwapBuffers(win); diff --git a/src/main.cpp b/src/main.cpp index 79e1d88..2dffe43 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,7 +27,7 @@ unsigned long get_now() int main() { graphics::window::create(); - sim::system sys(system::generate()); + sim::system sys; long clock = get_now(); diff --git a/src/reactor/builder.cpp b/src/reactor/builder.cpp index 58e9cf3..53495b4 100644 --- a/src/reactor/builder.cpp +++ b/src/reactor/builder.cpp @@ -1,11 +1,14 @@ #include "builder.hpp" +#include +#include + using namespace sim::reactor; -sim::reactor::reactor sim::reactor::builder(const int W, const int H, fuel::fuel_rod fr, control::control_rod cr, coolant::pipe p, const char** lines) +sim::reactor::reactor sim::reactor::builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, control::boron_rod br, coolant::vessel& v, const char** lines) { - std::unique_ptr arr[W * H]; + std::vector> arr(W * H); for(int y = 0; y < H; y++) for(int x = 0; x < W; x++) @@ -19,7 +22,7 @@ sim::reactor::reactor sim::reactor::builder(const int W, const int H, fuel::fuel r = new fuel::fuel_rod(fr); break; case 'C': - r = new control::control_rod(cr); + r = new control::boron_rod(br); break; case 'G': r = new control::graphite_rod(); @@ -27,10 +30,10 @@ sim::reactor::reactor sim::reactor::builder(const int W, const int H, fuel::fuel case 'H': r = new coolant::heater(); break; - case ' ': - r = new coolant::pipe(p); + case 'P': + r = new coolant::pipe(v); break; - case '#': + case ' ': r = new rod(); break; } @@ -38,6 +41,6 @@ sim::reactor::reactor sim::reactor::builder(const int W, const int H, fuel::fuel arr[y * W + x] = std::unique_ptr(std::move(r)); } - return reactor(arr, W, H); + return reactor(&arr[0], W, H, CW, CH); } diff --git a/src/reactor/builder.hpp b/src/reactor/builder.hpp index 79ba7ba..8bfa264 100644 --- a/src/reactor/builder.hpp +++ b/src/reactor/builder.hpp @@ -3,16 +3,17 @@ #include "rod.hpp" #include "fuel/fuel_rod.hpp" -#include "control/control_rod.hpp" +#include "control/boron_rod.hpp" #include "control/graphite_rod.hpp" #include "coolant/pipe.hpp" #include "coolant/heater.hpp" +#include "coolant/vessel.hpp" #include "reactor.hpp" namespace sim::reactor { -reactor builder(const int W, const int H, fuel::fuel_rod fr, control::control_rod cr, coolant::pipe p, const char** lines); +reactor builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, control::boron_rod br, coolant::vessel& v, const char** lines); }; diff --git a/src/reactor/control/boron_rod.cpp b/src/reactor/control/boron_rod.cpp new file mode 100644 index 0000000..7a9e6e8 --- /dev/null +++ b/src/reactor/control/boron_rod.cpp @@ -0,0 +1,61 @@ + +#include "boron_rod.hpp" + +#include + +using namespace sim::reactor::control; + +constexpr double boron_density = 2340000; // g/m^3 +constexpr double boron_molar_mass = 10; // g/mol +constexpr double boron_molar_density = boron_density / boron_molar_mass; // mol/m^3 + +boron_rod::boron_rod(coolant::vessel& v, double max) : coolant::pipe(v), max(max) +{ + +} + +void boron_rod::display(std::ostream& o) const +{ + double limit = get_volume() * boron_molar_density; + + o << "Inserted: " << (inserted * 100) << "%\n"; + o << "Use: " << (absorbed * limit) << " / " << limit << "\n"; +}; + +void boron_rod::set_reactivity(double a) +{ + inserted = 1 - a; +} + +glm::vec4 boron_rod::get_colour() const +{ + double v = inserted * 0.75 + 0.25; + return {v, v, v, 1}; +} + +void boron_rod::update(double secs) +{ + double limit = get_volume() * boron_molar_density; + + update_rod(secs); + + double k = (1 - absorbed) * inserted * max; + double m = 1 - std::pow(0.5, secs * -std::log2(1 - k)); + double r_fast = vals[val_t::N_FAST] * m; + double r_slow = vals[val_t::N_SLOW] * m; + + vals[val_t::N_FAST] -= r_fast; + vals[val_t::N_SLOW] -= r_slow; + absorbed += (r_fast + r_slow) / limit; + + update_pipe(secs); +} + +void boron_rod::update_selected(double a) +{ + inserted += a; + + if(inserted > 1) inserted = 1; + if(inserted < 0) inserted = 0; +} + diff --git a/src/reactor/control/control_rod.hpp b/src/reactor/control/boron_rod.hpp similarity index 54% rename from src/reactor/control/control_rod.hpp rename to src/reactor/control/boron_rod.hpp index 655b74d..a5b59bf 100644 --- a/src/reactor/control/control_rod.hpp +++ b/src/reactor/control/boron_rod.hpp @@ -6,25 +6,26 @@ namespace sim::reactor::control { -class control_rod : public sim::reactor::coolant::pipe +class boron_rod : public sim::reactor::coolant::pipe { - const double limit; const double max; double inserted = 1; double absorbed = 0; virtual void display(std::ostream& o) const; - - virtual const char* get_name() const { return "Control Rod"; } - virtual rod* clone() const { return new control_rod(*this); }; + virtual const char* get_name() const { return "Boron Control Rod"; } + virtual rod* clone() const { return new boron_rod(*this); } + virtual glm::vec4 get_colour() const; + virtual int get_id() const { return 5; } public: - control_rod(coolant::vessel& v, double limit, double max); + boron_rod(coolant::vessel& v, double max); virtual void update(double secs); void set_reactivity(double a); + double get_inserted() { return inserted; } virtual bool should_display() const { return true; } virtual bool should_select() const { return true; } diff --git a/src/reactor/control/control_rod.cpp b/src/reactor/control/control_rod.cpp deleted file mode 100644 index e955b98..0000000 --- a/src/reactor/control/control_rod.cpp +++ /dev/null @@ -1,47 +0,0 @@ - -#include "control_rod.hpp" - -#include - -using namespace sim::reactor::control; - -control_rod::control_rod(coolant::vessel& v, double limit, double max) : coolant::pipe(v), limit(limit), max(max) -{ - -} - -void control_rod::display(std::ostream& o) const -{ - o << "Inserted: " << (inserted * 100) << "%\n"; - o << "Use: " << absorbed << " / " << limit << " mol\n"; -}; - -void control_rod::set_reactivity(double a) -{ - inserted = 1 - a; -} - -void control_rod::update(double secs) -{ - update_rod(secs); - - double k = (1 - absorbed / limit) * inserted * max; - double m = 1 - std::pow(0.5, secs * -std::log2(1 - k)); - double r_fast = vals[val_t::N_FAST] * m; - double r_slow = vals[val_t::N_SLOW] * m; - - vals[val_t::N_FAST] -= r_fast; - vals[val_t::N_SLOW] -= r_slow; - absorbed += r_fast + r_slow; - - update_pipe(secs); -} - -void control_rod::update_selected(double a) -{ - inserted += a; - - if(inserted > 1) inserted = 1; - if(inserted < 0) inserted = 0; -} - diff --git a/src/reactor/control/graphite_rod.cpp b/src/reactor/control/graphite_rod.cpp index 19f8907..7c87b9d 100644 --- a/src/reactor/control/graphite_rod.cpp +++ b/src/reactor/control/graphite_rod.cpp @@ -10,6 +10,12 @@ void graphite_rod::display(std::ostream& o) const o << "Inserted: " << (inserted * 100) << "%\n"; }; +glm::vec4 graphite_rod::get_colour() const +{ + double v = inserted * 0.75 + 0.25; + return {v, v, v, 1}; +} + double graphite_rod::get_k(val_t type) const { if(type == val_t::HEAT) return 0.5; diff --git a/src/reactor/control/graphite_rod.hpp b/src/reactor/control/graphite_rod.hpp index 698f742..a77b63e 100644 --- a/src/reactor/control/graphite_rod.hpp +++ b/src/reactor/control/graphite_rod.hpp @@ -11,10 +11,13 @@ class graphite_rod : public sim::reactor::rod double inserted = 0; virtual void display(std::ostream& o) const; - virtual const char* get_name() const { return "Graphite Rod"; } virtual rod* clone() const { return new graphite_rod(*this); }; virtual double get_k(val_t type) const; + virtual glm::vec4 get_colour() const; + virtual int get_id() const { return 4; } + + double get_inserted() { return inserted; } public: diff --git a/src/reactor/coolant/heater.hpp b/src/reactor/coolant/heater.hpp index f9fa1a4..c906335 100644 --- a/src/reactor/coolant/heater.hpp +++ b/src/reactor/coolant/heater.hpp @@ -12,9 +12,11 @@ class heater : public sim::reactor::rod virtual void display(std::ostream& o) const; + virtual bool has_sensors(val_t t) const { return true; } virtual const char* get_name() const { return "Heater"; } virtual double get_k(val_t type) const { return 0.5; } virtual rod* clone() const { return new heater(*this); }; + virtual int get_id() const { return 3; } public: diff --git a/src/reactor/coolant/pipe.cpp b/src/reactor/coolant/pipe.cpp index e78810e..7bcd4e0 100644 --- a/src/reactor/coolant/pipe.cpp +++ b/src/reactor/coolant/pipe.cpp @@ -1,5 +1,6 @@ #include "pipe.hpp" +#include "../reactor.hpp" using namespace sim::reactor::coolant; @@ -22,7 +23,10 @@ void pipe::update(double secs) void pipe::update_pipe(double secs) { - vals[val_t::HEAT] = vessel->add_heat(vals[val_t::HEAT]); + sim::reactor::reactor* r = (sim::reactor::reactor*)reactor; + double m = r->cell_width * r->cell_width * r->cell_height * 1e6; + + vals[val_t::HEAT] = vessel->add_heat(m, vals[val_t::HEAT]); vals[val_t::N_SLOW] += vals[val_t::N_FAST]; vals[val_t::N_FAST] = 0; } diff --git a/src/reactor/coolant/pipe.hpp b/src/reactor/coolant/pipe.hpp index b184bec..46deb5f 100644 --- a/src/reactor/coolant/pipe.hpp +++ b/src/reactor/coolant/pipe.hpp @@ -17,6 +17,7 @@ protected: virtual double get_k(sim::reactor::rod::val_t type) const; virtual const char* get_name() const { return "Coolant"; } virtual rod* clone() const { return new pipe(*this); }; + virtual int get_id() const { return 2; } void update_pipe(double secs); diff --git a/src/reactor/coolant/vessel.cpp b/src/reactor/coolant/vessel.cpp index 5cbd1ad..0c82ffe 100644 --- a/src/reactor/coolant/vessel.cpp +++ b/src/reactor/coolant/vessel.cpp @@ -65,15 +65,15 @@ void vessel::update(double secs) steam_suspended *= fuel::half_life::get(secs, bubble_hl); } -double vessel::add_heat(double t1) +double vessel::add_heat(double m1, double t1) { double t2 = get_heat(); double t = t1 - t2; - double m1 = 1e6; double m2 = (fluid.l_to_g(level) + steam) * fluid.jPgk; double m = m1 + m2; - return heat = t1 - t * m2 / m; + heat = t1 - t * m2 / m; + return heat; } double vessel::add_fluid(double m2, double t2) diff --git a/src/reactor/coolant/vessel.hpp b/src/reactor/coolant/vessel.hpp index f95a59d..99ddcac 100644 --- a/src/reactor/coolant/vessel.hpp +++ b/src/reactor/coolant/vessel.hpp @@ -11,7 +11,7 @@ namespace sim::reactor::coolant class vessel { double level; // litres - double heat = 0; // celcius + double heat = 0; // celsius double steam = 0; // grams double steam_suspended = 0; // grams @@ -27,7 +27,7 @@ public: vessel(double height, double diameter, double level, sim::coolant::fluid_t fluid); void update(double secs); - double add_heat(double amount); + double add_heat(double m, double t); double add_fluid(double amount, double heat); double extract_steam(double dt, double a, double p2); diff --git a/src/reactor/fuel/fuel_rod.cpp b/src/reactor/fuel/fuel_rod.cpp index fc96c2d..6951be9 100644 --- a/src/reactor/fuel/fuel_rod.cpp +++ b/src/reactor/fuel/fuel_rod.cpp @@ -1,33 +1,87 @@ #include "fuel_rod.hpp" +#include + using namespace sim::reactor::fuel; -fuel_rod::fuel_rod(double fuel, double mass) : s(fuel, mass) +constexpr double fuel_density = 19100000; // g/m^3 +constexpr double fuel_molar_mass = 238.029; // g/mol +constexpr double fuel_molar_density = fuel_density / fuel_molar_mass; // mol/m^3 +constexpr double energy_density = 165e11; // J/mol + +fuel_rod::fuel_rod(double fuel) : s(fuel) { } void fuel_rod::display(std::ostream& o) const { - o << "Fuel: " << s.get_fuel() << " / " << s.get_mass() << " mol\n"; + double mol = fuel_molar_density * get_volume(); + + o << "Fuel: " << (s.get_fuel() * mol) << " / " << (s.get_mass() * mol) << " mol\n"; o << "Efficiency: " << (s.get_efficiency() * 100) << " %\n"; - o << "Output: " << s.get_energy() << " W/s\n"; - o << "Iodine: " << s.get_i_135() << " mol\n"; - o << "Xenon: " << s.get_xe_135() << " mol\n"; + o << "Output: " << (s.get_energy() * mol * energy_density) << " W/s\n"; + o << "Iodine: " << (s.get_i_135() * mol) << " mol\n"; + o << "Xenon: " << (s.get_xe_135() * mol) << " mol\n"; +} + +static float map(float v, float imin, float imax, float omin, float omax) +{ + return (v - imin) * (omax - omin) / (imax - imin) + omin; +} + +glm::vec4 fuel_rod::get_colour() const +{ + double temp = vals[val_t::HEAT]; + + if(temp < 0) + { + temp = 0; + } + + // this should not happen + if(std::isnan(temp)) + { + return {1, 0, 1, 1}; + } + + if(temp < 120) + { + return {0, map(temp, 0, 120, 0, 1), 1, 1}; + } + + if(temp < 240) + { + return {0, 1, map(temp, 120, 240, 1, 0), 1}; + } + + if(temp < 280) + { + return {map(temp, 240, 280, 0, 1), 1, 0, 1}; + } + + if(temp < 320) + { + return {1, map(temp, 280, 320, 1, 0), 0, 1}; + } + + return {1, 0, 0, 1}; } void fuel_rod::update(double secs) { update_rod(secs); + double mol = fuel_molar_density * get_volume(); + s.clear_energy(); s.clear_fast_neutrons(); - s.add_slow_neutrons(vals[val_t::N_SLOW]); + s.add_slow_neutrons(vals[val_t::N_SLOW] / mol); s.update(secs); - vals[val_t::HEAT] += s.get_energy() * secs; - vals[val_t::N_FAST] += s.get_fast_neutrons(); + vals[val_t::HEAT] += s.get_energy() * mol * energy_density * secs; + vals[val_t::N_FAST] += s.get_fast_neutrons() * mol; vals[val_t::N_SLOW] = 0; } diff --git a/src/reactor/fuel/fuel_rod.hpp b/src/reactor/fuel/fuel_rod.hpp index 68a6635..1e1b12d 100644 --- a/src/reactor/fuel/fuel_rod.hpp +++ b/src/reactor/fuel/fuel_rod.hpp @@ -14,15 +14,17 @@ class fuel_rod : public sim::reactor::rod virtual double get_k(val_t type) const { return 0.5; } virtual void display(std::ostream& o) const; + virtual bool has_sensors(val_t t) const { return true; } virtual const char* get_name() const { return "Fuel"; } virtual rod* clone() const { return new fuel_rod(*this); }; + virtual int get_id() const { return 1; } + virtual glm::vec4 get_colour() const; public: - fuel_rod(double fuel, double mass); + fuel_rod(double fuel); virtual bool should_display() const { return true; } - virtual void update(double secs); }; diff --git a/src/reactor/fuel/sample.cpp b/src/reactor/fuel/sample.cpp index 8ded7d5..8021191 100644 --- a/src/reactor/fuel/sample.cpp +++ b/src/reactor/fuel/sample.cpp @@ -6,12 +6,12 @@ using namespace sim::reactor::fuel; -static const double NEUTRON_BG = 1e-10; +static const double NEUTRON_BG = 1e-25; -sample::sample(double fuel, double mass) +sample::sample(double fuel) { this->fuel = fuel; - this->mass = mass; + this->mass = 1; } void sample::update(double secs) @@ -21,7 +21,7 @@ void sample::update(double secs) // decay waste and extract products waste.update(secs); fast_neutrons += waste.extract_neutrons(); - energy += waste.extract_energy(); + energy += waste.extract_energy() * (1.0 / 30.0); // decay Xe-135 m = half_life::get(secs, half_life::Xe_135); @@ -59,7 +59,7 @@ void sample::update(double secs) efficiency = neutrons_fuel / neutrons_total; // simulate fuel use - energy += neutrons_fuel / secs; + energy += neutrons_fuel / secs * 0.8; waste.add_fissile(neutrons_fuel * 6); } diff --git a/src/reactor/fuel/sample.hpp b/src/reactor/fuel/sample.hpp index c69cacc..4579c0c 100644 --- a/src/reactor/fuel/sample.hpp +++ b/src/reactor/fuel/sample.hpp @@ -31,7 +31,7 @@ class sample public: - sample(double fuel, double mass); + sample(double fuel); void update(double secs); diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index 32ee09d..73b2a22 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -6,24 +6,25 @@ using namespace sim::reactor; -reactor::reactor(std::unique_ptr* rods, int width, int height) : width(width), height(height), size(width * height) +reactor::reactor(std::unique_ptr* rods, int w, int h, double cw, double ch) : cell_width(cw), cell_height(ch), width(w), height(h), size(w * h) { this->rods = new std::unique_ptr[width * height]; for(int i = 0; i < size; i++) { this->rods[i] = std::move(rods[i]); + this->rods[i]->reactor = this; } } -reactor::reactor(reactor&& o) : width(o.width), height(o.height), size(o.size) +reactor::reactor(reactor&& o) : cell_width(o.cell_width), cell_height(o.cell_height), width(o.width), height(o.height), size(o.size) { rods = o.rods; cursor = o.cursor; o.rods = nullptr; } -reactor::reactor(const reactor& o) : width(o.width), height(o.height), size(o.size) +reactor::reactor(const reactor& o) : cell_width(o.cell_width), cell_height(o.cell_height), width(o.width), height(o.height), size(o.size) { rods = new std::unique_ptr[width * height]; @@ -132,3 +133,35 @@ void reactor::update_interactions(int* rods_lookup, double secs) } } } + +double reactor::get_total(rod::val_t type) +{ + double v = 0; + + for(int i = 0; i < size; i++) + { + v += rods[i]->get(type); + } + + return v; +} + +void reactor::get_stats(rod::val_t type, double& min, double& max) +{ + min = INFINITY; + max = -INFINITY; + + for(int i = 0; i < size; i++) + { + if(!rods[i]->has_sensors(type)) + { + continue; + } + + double v = rods[i]->get(type); + + if(v > max) max = v; + if(v < min) min = v; + } +} + diff --git a/src/reactor/reactor.hpp b/src/reactor/reactor.hpp index 0a64139..55e7e38 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -12,6 +12,8 @@ namespace sim::reactor struct reactor { + const double cell_width; + const double cell_height; const int width; const int height; const int size; @@ -19,13 +21,16 @@ struct reactor std::unique_ptr* rods; int cursor = 0; - reactor(std::unique_ptr* rods, int width, int height); + reactor(std::unique_ptr* rods, int width, int height, double cell_width, double cell_height); reactor(const reactor& r); reactor(reactor&& r); ~reactor(); void update(double secs); void update_selected(int v); + void get_stats(rod::val_t type, double& min, double& max); + void get_rod_stats(int type, double& min, double& max); + double get_total(rod::val_t type); int move_cursor(int d); void toggle_selected(); diff --git a/src/reactor/rod.cpp b/src/reactor/rod.cpp index 75b02c5..d744e5d 100644 --- a/src/reactor/rod.cpp +++ b/src/reactor/rod.cpp @@ -1,5 +1,6 @@ #include "rod.hpp" +#include "reactor.hpp" #include @@ -48,6 +49,12 @@ double rod::get_speed() const return motion == 0 ? 0 : (std::pow(10, std::abs(motion)) * 1e-6 * m); } +double rod::get_volume() const +{ + auto r = (sim::reactor::reactor*)reactor; + return r->cell_width * r->cell_width * r->cell_height; +} + void rod::update_rod(double secs) { // decay the free neutrons diff --git a/src/reactor/rod.hpp b/src/reactor/rod.hpp index 685cb86..755e896 100644 --- a/src/reactor/rod.hpp +++ b/src/reactor/rod.hpp @@ -2,6 +2,7 @@ #pragma once #include +#include namespace sim::reactor { @@ -9,7 +10,8 @@ namespace sim::reactor class rod { public: - + + void* reactor = nullptr; static const int VAL_N = 3; enum val_t @@ -20,15 +22,19 @@ public: }; virtual void interact(rod* o, double secs); - virtual void update(double secs) { }; + virtual void update(double secs) { } virtual void add(val_t type, double v); virtual double extract(val_t type, double s, double k, double o); virtual double get(val_t type) const; - virtual rod* clone() const { return new rod(*this); }; + virtual rod* clone() const { return new rod(*this); } + virtual glm::vec4 get_colour() const { return {0, 0, 0, 0}; } + virtual int get_id() const { return 0; } + virtual bool has_sensors(val_t t) const { return false; } virtual bool should_display() const { return false; } virtual bool should_select() const { return false; } void update_rod_selected(int m); + double get_volume() const; constexpr void toggle_selected() { selected = !selected; } constexpr bool is_selected() const { return selected; } diff --git a/src/system.cpp b/src/system.cpp index 748f4ad..c2e1759 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -2,46 +2,74 @@ #include "system.hpp" #include "reactor/builder.hpp" -#include "reactor/control/control_rod.hpp" +#include "reactor/control/boron_rod.hpp" #include "reactor/fuel/fuel_rod.hpp" #include "reactor/coolant/pipe.hpp" #include "reactor/coolant/heater.hpp" using namespace sim; -sim::system sim::system::generate() +system::system() { const char* layout[] = { - "#C#C#", - "CFCFC", - "#C#C#", - "CFCFC", - "#C#C#" + " C C C C ", + " C CFCFCFCFC C ", + " CFCFCFCFCFCFCFC ", + " CFCFCFCFCFCFCFCFC ", + " CFCFCFCFCFCFCFC ", + " CFCFCFCFCFCFCFCFC ", + "CFCFCFCFCFCFCFCFCFC", + " CFCFCFCFCFCFCFCFC ", + "CFCFCFCFCFCFCFCFCFC", + " CFCFCFCFCFCFCFCFC ", + "CFCFCFCFCFCFCFCFCFC", + " CFCFCFCFCFCFCFCFC ", + "CFCFCFCFCFCFCFCFCFC", + " CFCFCFCFCFCFCFCFC ", + " CFCFCFCFCFCFCFC ", + " CFCFCFCFCFCFCFCFC ", + " CFCFCFCFCFCFCFC ", + " C CFCFCFCFC C ", + " C C C C " }; - reactor::coolant::vessel vessel(8, 10, 300, sim::coolant::WATER); - sim::reactor::reactor reactor(sim::reactor::builder(5, 5, - reactor::fuel::fuel_rod(2000, 4000), - reactor::control::control_rod(vessel, 10000, 1), - reactor::coolant::pipe(vessel), - layout)); + vessel = new reactor::coolant::vessel(8, 10, 300, sim::coolant::WATER); + reactor = new sim::reactor::reactor(sim::reactor::builder(19, 19, 0.25, 8, + reactor::fuel::fuel_rod(0.5), + reactor::control::boron_rod(*vessel, 1), + *vessel, layout)); - coolant::valve valve(vessel, 1, 500); - coolant::pump pump(vessel, 1e4, 15); + valve = new coolant::valve(*vessel, 1, 500); + pump = new coolant::pump(*vessel, 1e4, 15); +} - return { - .reactor = reactor, - .vessel = vessel, - .valve = valve, - .pump = pump - }; +system::system(system&& o) +{ + vessel = o.vessel; + reactor = o.reactor; + valve = o.valve; + pump = o.pump; + + o.vessel = nullptr; + o.reactor = nullptr; + o.valve = nullptr; + o.pump = nullptr; +} + +system::~system() +{ + if(vessel != nullptr) delete vessel; + if(reactor != nullptr) delete reactor; + if(valve != nullptr) delete valve; + if(pump != nullptr) delete pump; } void system::update(double dt) { - vessel.update(dt); - reactor.update(dt); - valve.update(dt); - pump.update(dt); + dt *= 10; + vessel->update(dt); + reactor->update(dt); + valve->update(dt); + pump->update(dt); } diff --git a/src/system.hpp b/src/system.hpp index b52d872..a8154b1 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -1,6 +1,8 @@ #pragma once +#include + #include "reactor/coolant/vessel.hpp" #include "reactor/reactor.hpp" #include "coolant/pump.hpp" @@ -11,13 +13,17 @@ namespace sim struct system { - sim::reactor::reactor reactor; - sim::reactor::coolant::vessel vessel; - sim::coolant::valve valve; - sim::coolant::pump pump; + sim::reactor::reactor* reactor; + sim::reactor::coolant::vessel* vessel; + sim::coolant::valve* valve; + sim::coolant::pump* pump; + + system(); + system(system&& o); + system(const system& o) = delete; + ~system(); void update(double dt); - static system generate(); }; }; From 821897bbaa090c473f978435788f9098db0a93b2 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Mon, 29 Jan 2024 22:05:12 +1100 Subject: [PATCH 23/43] added a proper collision system --- src/graphics/camera.cpp | 47 ++++++--------------- src/graphics/camera.hpp | 4 +- src/graphics/mesh/mesh.cpp | 85 ++++++++++++++++++++++++++++++++++++++ src/graphics/mesh/mesh.hpp | 2 + src/main.cpp | 4 +- src/system.cpp | 2 + src/system.hpp | 2 + 7 files changed, 110 insertions(+), 36 deletions(-) diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index b855ae5..2c4f94d 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -14,6 +14,7 @@ using namespace sim::graphics; +static bool on_ground = false; static double yaw = 0, pitch = 0; static glm::vec<3, double> pos(0, 0, 2); static glm::vec<3, double> velocity(0); @@ -35,7 +36,7 @@ void camera::move(double xoff, double yoff, double zoff) pos.z += zoff; } -void camera::update(double dt) +void camera::update(const system& sys, double dt) { glm::vec<2, double> off(0, 0); double m = 30; @@ -61,46 +62,24 @@ void camera::update(double dt) }; glm::vec<2, double> rotated = glm::vec<2, double>(off.x, off.y) * mat; - bool on_ground = false; - velocity.z -= 9.81 * dt; - if(pos.z + velocity.z * dt < 1.6) + if(on_ground) { - on_ground = true; - - if(keyboard::is_pressed(GLFW_KEY_SPACE)) - { - velocity.z = 3.5; - } - - else - { - velocity.z = 0; - pos.z = 1.6; - } - } - - else - { - m = 0; + velocity.x += rotated.x * m * dt; + velocity.y += rotated.y * m * dt; } - velocity.x += rotated.x * m * dt; - velocity.y += rotated.y * m * dt; + if(on_ground && keyboard::is_pressed(GLFW_KEY_SPACE)) + { + velocity.z += 3.5; + } - double nx = pos.x + velocity.x * dt; - double ny = pos.y + velocity.y * dt; + glm::vec<3, double> velocity2 = sys.scene.check_intersect(pos, velocity * dt) / dt; - if(nx > 8.9 || nx < -2.9) - velocity.x = 0; - if(std::abs(ny) > 3.9) - velocity.y = 0; - - float m2 = std::pow(0.5, dt / (on_ground ? 0.05 : 1)); - - pos += velocity * dt; - velocity *= m2; + pos += velocity2 * dt; + on_ground = ((velocity * dt / dt).z != velocity2.z); + velocity = velocity2 * std::pow(0.5, dt / (on_ground ? 0.05 : 10)); camera_mat = glm::mat4(1); camera_mat = glm::rotate(camera_mat, (float)glm::radians(-pitch), glm::vec3(1, 0, 0)); diff --git a/src/graphics/camera.hpp b/src/graphics/camera.hpp index 93ddc1c..7d08761 100644 --- a/src/graphics/camera.hpp +++ b/src/graphics/camera.hpp @@ -4,13 +4,15 @@ #include #include +#include "../system.hpp" + namespace sim::graphics::camera { glm::mat4 get_matrix(); void rotate(double pitch, double yaw); void move(double x, double y, double z); -void update(double dt); +void update(const system& sys, double dt); }; diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp index a411602..93a01ca 100644 --- a/src/graphics/mesh/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -3,6 +3,9 @@ #include "arrays.hpp" #include "../shader.hpp" #include "../camera.hpp" +#include "../../math.hpp" + +#include using namespace sim::graphics; @@ -50,3 +53,85 @@ void mesh::set_indices(const unsigned int* data, size_t size) } } +typedef glm::vec<3, double> vec3; + +bool ray_intersects_triangle(vec3 ray_origin, + vec3 ray_vector, + const vec3* triangle, + vec3& out_intersection_point) +{ + constexpr double epsilon = std::numeric_limits::epsilon(); + + vec3 edge1 = triangle[1] - triangle[0]; + vec3 edge2 = triangle[2] - triangle[0]; + vec3 ray_cross_e2 = cross(ray_vector, edge2); + double det = dot(edge1, ray_cross_e2); + + if (det > -epsilon && det < epsilon) + return false; // This ray is parallel to this triangle. + + double inv_det = 1.0 / det; + vec3 s = ray_origin - triangle[0]; + double u = inv_det * dot(s, ray_cross_e2); + + if (u < 0 || u > 1) + return false; + + vec3 s_cross_e1 = cross(s, edge1); + double v = inv_det * dot(ray_vector, s_cross_e1); + + if (v < 0 || u + v > 1) + return false; + + // At this stage we can compute t to find out where the intersection point is on the line. + double t = inv_det * dot(edge2, s_cross_e1); + out_intersection_point = ray_origin + ray_vector * t; + + if (t > epsilon) // ray intersection + { + return true; + } + else // This means that there is a line intersection but not a ray intersection. + return false; +} + +vec3 mesh::check_intersect(vec3 pos, vec3 path) const +{ + double l = glm::length(path); + + if(l == 0) + { + return path; + } + + vec3 path_n = path / l; + bool intersects = false; + + for(unsigned int i = 0; i < indices.size(); i += 3) + { + vec3 v[3] = { + vec3(this->vertices[indices[i]].pos), + vec3(this->vertices[indices[i + 1]].pos), + vec3(this->vertices[indices[i + 2]].pos) + }; + + vec3 ipoint; + vec3 normal = glm::normalize(glm::cross(v[1] - v[0], v[2] - v[0])); + double d = glm::dot(normal, path); + + if(d >= 0) + continue; + if(!ray_intersects_triangle(pos, path_n, v, ipoint)) + continue; + if(l < glm::length(ipoint - pos)) + continue; + + intersects = true; + path -= normal * d; + l = glm::length(path); + path_n = path / l; + } + + return path; +} + diff --git a/src/graphics/mesh/mesh.hpp b/src/graphics/mesh/mesh.hpp index 698cbc1..115d23e 100644 --- a/src/graphics/mesh/mesh.hpp +++ b/src/graphics/mesh/mesh.hpp @@ -27,6 +27,8 @@ struct mesh void load_text(const char* text, double size); void add(const mesh& o, glm::mat4 mat); + glm::vec<3, double> check_intersect(glm::vec<3, double> pos, glm::vec<3, double> path) const; + template void load_text(const char* header, T& item, double size) { diff --git a/src/main.cpp b/src/main.cpp index 2dffe43..68f7277 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,8 @@ #include "coolant/valve.hpp" #include "coolant/pump.hpp" +#include "graphics/mesh/mesh.hpp" + #include "graphics/window.hpp" #include "graphics/camera.hpp" @@ -39,7 +41,7 @@ int main() clock += passed; sys.update(dt); - graphics::camera::update(dt); + graphics::camera::update(sys, dt); graphics::window::loop(sys); } diff --git a/src/system.cpp b/src/system.cpp index c2e1759..7785f24 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -41,6 +41,8 @@ system::system() valve = new coolant::valve(*vessel, 1, 500); pump = new coolant::pump(*vessel, 1e4, 15); + + scene.load_model("../assets/model", "scene_collisions.stl"); } system::system(system&& o) diff --git a/src/system.hpp b/src/system.hpp index a8154b1..36b0f4a 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -7,6 +7,7 @@ #include "reactor/reactor.hpp" #include "coolant/pump.hpp" #include "coolant/valve.hpp" +#include "graphics/mesh/mesh.hpp" namespace sim { @@ -17,6 +18,7 @@ struct system sim::reactor::coolant::vessel* vessel; sim::coolant::valve* valve; sim::coolant::pump* pump; + sim::graphics::mesh scene; system(); system(system&& o); From 93555ddac45def2fce8dcc4af86768f8566ff2ef Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Tue, 30 Jan 2024 16:09:43 +1100 Subject: [PATCH 24/43] fix memory issues --- src/graphics/camera.cpp | 8 +- src/graphics/mesh/font.cpp | 2 + src/graphics/mesh/glmesh.cpp | 7 +- src/graphics/mesh/glmesh.hpp | 1 + src/graphics/mesh/mesh.cpp | 116 ++++++++++++++++++++++++--- src/graphics/mesh/mesh.hpp | 5 +- src/graphics/window.cpp | 17 ++-- src/graphics/window.hpp | 2 +- src/main.cpp | 2 +- src/reactor/builder.cpp | 17 ++-- src/reactor/control/boron_rod.hpp | 2 +- src/reactor/control/graphite_rod.hpp | 2 +- src/reactor/coolant/heater.hpp | 2 +- src/reactor/coolant/pipe.hpp | 2 +- src/reactor/fuel/fuel_rod.hpp | 2 +- src/reactor/reactor.cpp | 15 +--- src/reactor/reactor.hpp | 3 +- src/reactor/rod.hpp | 4 +- src/system.cpp | 35 +++----- src/system.hpp | 9 +-- src/{ => util}/math.hpp | 0 21 files changed, 173 insertions(+), 80 deletions(-) rename src/{ => util}/math.hpp (100%) diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index 2c4f94d..93f8aec 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -4,7 +4,7 @@ #include "camera.hpp" #include "input/keyboard.hpp" -#include "../math.hpp" +#include "../util/math.hpp" #include #include @@ -75,7 +75,11 @@ void camera::update(const system& sys, double dt) velocity.z += 3.5; } - glm::vec<3, double> velocity2 = sys.scene.check_intersect(pos, velocity * dt) / dt; + glm::vec<3, double> normal_last(0); + glm::vec<3, double> velocity2; + + velocity2 = sys.scene.calc_intersect(pos, velocity * dt, normal_last); + velocity2 = sys.scene.calc_intersect(pos + glm::vec<3, double>(0, 0, -1.5), velocity2, normal_last) / dt; pos += velocity2 * dt; on_ground = ((velocity * dt / dt).z != velocity2.z); diff --git a/src/graphics/mesh/font.cpp b/src/graphics/mesh/font.cpp index 846e68d..0db843a 100644 --- a/src/graphics/mesh/font.cpp +++ b/src/graphics/mesh/font.cpp @@ -89,6 +89,8 @@ void font::init() chars[i] = c; } + + FT_Done_FreeType(ft); } void mesh::load_text(const char* text, double size) diff --git a/src/graphics/mesh/glmesh.cpp b/src/graphics/mesh/glmesh.cpp index 9e9b47e..ea8e82a 100644 --- a/src/graphics/mesh/glmesh.cpp +++ b/src/graphics/mesh/glmesh.cpp @@ -73,6 +73,11 @@ void glmesh::set(const mesh& m, int mode) void glmesh::render() { - glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0); + render(GL_TRIANGLES); +} + +void glmesh::render(int type) +{ + glDrawElements(type, size, GL_UNSIGNED_INT, 0); } diff --git a/src/graphics/mesh/glmesh.hpp b/src/graphics/mesh/glmesh.hpp index ff04e6d..b8a2223 100644 --- a/src/graphics/mesh/glmesh.hpp +++ b/src/graphics/mesh/glmesh.hpp @@ -27,6 +27,7 @@ struct glmesh void bind(); void uniform(); void set(const mesh& m, int mode); + void render(int type); void render(); }; diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp index 93a01ca..7f60a12 100644 --- a/src/graphics/mesh/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -3,7 +3,7 @@ #include "arrays.hpp" #include "../shader.hpp" #include "../camera.hpp" -#include "../../math.hpp" +#include "../../util/math.hpp" #include @@ -95,19 +95,18 @@ bool ray_intersects_triangle(vec3 ray_origin, return false; } -vec3 mesh::check_intersect(vec3 pos, vec3 path) const +bool mesh::check_intersect(vec3 pos, vec3 path) const { double l = glm::length(path); if(l == 0) { - return path; + return false; } vec3 path_n = path / l; - bool intersects = false; - - for(unsigned int i = 0; i < indices.size(); i += 3) + + for(unsigned int i = 0; i < indices.size(); i++) { vec3 v[3] = { vec3(this->vertices[indices[i]].pos), @@ -117,7 +116,7 @@ vec3 mesh::check_intersect(vec3 pos, vec3 path) const vec3 ipoint; vec3 normal = glm::normalize(glm::cross(v[1] - v[0], v[2] - v[0])); - double d = glm::dot(normal, path); + double d = glm::dot(normal, path_n); if(d >= 0) continue; @@ -126,12 +125,107 @@ vec3 mesh::check_intersect(vec3 pos, vec3 path) const if(l < glm::length(ipoint - pos)) continue; - intersects = true; - path -= normal * d; - l = glm::length(path); - path_n = path / l; + return true; } + return false; +} + +vec3 mesh::calc_intersect(vec3 pos, vec3 path) const +{ + vec3 normal_last(0); + return calc_intersect(pos, path, normal_last); +} + +static bool calc_intercept_vert(vec3 v[3], vec3 pos, vec3& path, vec3& path_n, vec3& normal_last, double& l) +{ + vec3 ipoint; + vec3 normal = glm::normalize(glm::cross(v[1] - v[0], v[2] - v[0])); + double d = glm::dot(normal, path); + + if(d >= 0) + return false; + if(!ray_intersects_triangle(pos, path_n, v, ipoint)) + return false; + if(l < glm::length(ipoint - pos)) + return false; + + if(normal_last != vec3(0)) + { + vec3 n = glm::cross(normal_last, normal); + + if(glm::length(n) > 0) + { + normal = glm::normalize(glm::cross(glm::cross(normal_last, normal), normal_last)); + d = glm::dot(normal, path); + } + } + + path -= normal * d; + normal_last = normal; + l = glm::length(path); + path_n = path / l; + + return true; +} + +vec3 mesh::calc_intersect(vec3 pos, vec3 path, vec3& normal_last) const +{ + double l = glm::length(path); + + if(l == 0) + { + return path; + } + + vec3 path_n = path / l; + unsigned int i_found = 0; + + for(unsigned int i = 0; i < indices.size(); i += 3) + { + vec3 v[3] = { + vec3(this->vertices[indices[i]].pos), + vec3(this->vertices[indices[i + 1]].pos), + vec3(this->vertices[indices[i + 2]].pos) + }; + + if(calc_intercept_vert(v, pos, path, path_n, normal_last, l)) + { + i_found = i; + } + } +/* + for(unsigned int i = i_found - 1; i >= 0; i--) + { + vec3 v[3] = { + vec3(this->vertices[indices[i]].pos), + vec3(this->vertices[indices[i + 1]].pos), + vec3(this->vertices[indices[i + 2]].pos) + }; + + calc_intercept_vert(v, pos, path, path_n, normal_last, l); + } +*/ return path; } +mesh mesh::to_lines() const +{ + mesh m; + m.vertices = vertices; + + for(int i = 0; i < indices.size(); i += 3) + { + m.indices.push_back(indices[i]); + m.indices.push_back(indices[i + 1]); + m.indices.push_back(indices[i + 1]); + m.indices.push_back(indices[i + 2]); + m.indices.push_back(indices[i]); + m.indices.push_back(indices[i + 2]); + } + + std::cout << "indices.size() = " << m.indices.size() << ", vertices.size() = " << m.vertices.size() << "\n"; + + return m; +} + diff --git a/src/graphics/mesh/mesh.hpp b/src/graphics/mesh/mesh.hpp index 115d23e..7c54f5b 100644 --- a/src/graphics/mesh/mesh.hpp +++ b/src/graphics/mesh/mesh.hpp @@ -27,7 +27,10 @@ struct mesh void load_text(const char* text, double size); void add(const mesh& o, glm::mat4 mat); - glm::vec<3, double> check_intersect(glm::vec<3, double> pos, glm::vec<3, double> path) const; + mesh to_lines() const; + bool check_intersect(glm::vec<3, double> pos, glm::vec<3, double> path) const; + glm::vec<3, double> calc_intersect(glm::vec<3, double> pos, glm::vec<3, double> path) const; + glm::vec<3, double> calc_intersect(glm::vec<3, double> pos, glm::vec<3, double> path, glm::vec<3, double>& normal_last) const; template void load_text(const char* header, T& item, double size) diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index b09996a..15f0cfc 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -28,7 +28,7 @@ using namespace sim::graphics; static GLFWwindow* win; static bool win_should_close = false; -static glmesh MeshScene; +static glmesh MeshScene, MeshCollisionScene; static monitor::vessel MonitorVessel; static monitor::core MonitorCore; @@ -40,7 +40,7 @@ void GLAPIENTRY cb_debug_message(GLenum source, GLenum type, GLuint id, GLenum s } } -void window::create() +void window::create(system& sys) { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); @@ -96,11 +96,14 @@ void window::create() font::init(); shader::init_program(); - mesh rmesh; - rmesh.load_model("../assets", "scene-baked.glb"); + sys.scene.load_model("../assets", "scene-baked.glb"); MeshScene.bind(); - MeshScene.set(rmesh, GL_STATIC_DRAW); + MeshScene.set(sys.scene, GL_STATIC_DRAW); + + sys.scene.load_model("../assets/model", "scene_collisions.stl"); + MeshCollisionScene.bind(); + MeshCollisionScene.set(sys.scene.to_lines(), GL_STATIC_DRAW); MonitorCore.init(); MonitorVessel.init(); @@ -123,6 +126,10 @@ void window::loop(sim::system& sys) MeshScene.uniform(); MeshScene.render(); + MeshCollisionScene.bind(); + MeshCollisionScene.uniform(); + MeshCollisionScene.render(GL_LINES); + MonitorCore.render(sys); MonitorVessel.render(); diff --git a/src/graphics/window.hpp b/src/graphics/window.hpp index 52e2368..f9222d8 100644 --- a/src/graphics/window.hpp +++ b/src/graphics/window.hpp @@ -8,8 +8,8 @@ namespace sim::graphics::window { -void create(); bool should_close(); +void create(sim::system& sys); void loop(sim::system& sys); void destroy(); void close(); diff --git a/src/main.cpp b/src/main.cpp index 68f7277..d7f2a2d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,8 +28,8 @@ unsigned long get_now() int main() { - graphics::window::create(); sim::system sys; + graphics::window::create(sys); long clock = get_now(); diff --git a/src/reactor/builder.cpp b/src/reactor/builder.cpp index 53495b4..87ad129 100644 --- a/src/reactor/builder.cpp +++ b/src/reactor/builder.cpp @@ -3,6 +3,7 @@ #include #include +#include using namespace sim::reactor; @@ -14,31 +15,31 @@ sim::reactor::reactor sim::reactor::builder(const int W, const int H, const doub for(int x = 0; x < W; x++) { char c = lines[y][x]; - rod* r; + std::unique_ptr r; switch(c) { case 'F': - r = new fuel::fuel_rod(fr); + r = fr.clone(); break; case 'C': - r = new control::boron_rod(br); + r = br.clone(); break; case 'G': - r = new control::graphite_rod(); + r = std::make_unique(); break; case 'H': - r = new coolant::heater(); + r = std::make_unique(); break; case 'P': - r = new coolant::pipe(v); + r = std::make_unique(v); break; case ' ': - r = new rod(); + r = std::make_unique(); break; } - arr[y * W + x] = std::unique_ptr(std::move(r)); + arr[y * W + x] = std::move(r); } return reactor(&arr[0], W, H, CW, CH); diff --git a/src/reactor/control/boron_rod.hpp b/src/reactor/control/boron_rod.hpp index a5b59bf..f7347c2 100644 --- a/src/reactor/control/boron_rod.hpp +++ b/src/reactor/control/boron_rod.hpp @@ -15,7 +15,6 @@ class boron_rod : public sim::reactor::coolant::pipe virtual void display(std::ostream& o) const; virtual const char* get_name() const { return "Boron Control Rod"; } - virtual rod* clone() const { return new boron_rod(*this); } virtual glm::vec4 get_colour() const; virtual int get_id() const { return 5; } @@ -30,6 +29,7 @@ public: virtual bool should_display() const { return true; } virtual bool should_select() const { return true; } virtual void update_selected(double a); + virtual std::unique_ptr clone() const { return std::make_unique(*this); } }; } diff --git a/src/reactor/control/graphite_rod.hpp b/src/reactor/control/graphite_rod.hpp index a77b63e..f5b8316 100644 --- a/src/reactor/control/graphite_rod.hpp +++ b/src/reactor/control/graphite_rod.hpp @@ -12,7 +12,6 @@ class graphite_rod : public sim::reactor::rod virtual void display(std::ostream& o) const; virtual const char* get_name() const { return "Graphite Rod"; } - virtual rod* clone() const { return new graphite_rod(*this); }; virtual double get_k(val_t type) const; virtual glm::vec4 get_colour() const; virtual int get_id() const { return 4; } @@ -27,6 +26,7 @@ public: virtual bool should_display() const { return true; } virtual bool should_select() const { return true; } virtual void update_selected(double a); + virtual std::unique_ptr clone() const { return std::make_unique(*this); } }; } diff --git a/src/reactor/coolant/heater.hpp b/src/reactor/coolant/heater.hpp index c906335..75958f0 100644 --- a/src/reactor/coolant/heater.hpp +++ b/src/reactor/coolant/heater.hpp @@ -15,7 +15,6 @@ class heater : public sim::reactor::rod virtual bool has_sensors(val_t t) const { return true; } virtual const char* get_name() const { return "Heater"; } virtual double get_k(val_t type) const { return 0.5; } - virtual rod* clone() const { return new heater(*this); }; virtual int get_id() const { return 3; } public: @@ -24,6 +23,7 @@ public: virtual bool should_display() const { return true; } virtual bool should_select() const { return true; } virtual void update_selected(double a); + virtual std::unique_ptr clone() const { return std::make_unique(*this); } }; }; diff --git a/src/reactor/coolant/pipe.hpp b/src/reactor/coolant/pipe.hpp index 46deb5f..71cd9c1 100644 --- a/src/reactor/coolant/pipe.hpp +++ b/src/reactor/coolant/pipe.hpp @@ -16,7 +16,6 @@ protected: virtual double get_k(sim::reactor::rod::val_t type) const; virtual const char* get_name() const { return "Coolant"; } - virtual rod* clone() const { return new pipe(*this); }; virtual int get_id() const { return 2; } void update_pipe(double secs); @@ -25,6 +24,7 @@ public: pipe(coolant::vessel& v); + virtual std::unique_ptr clone() const { return std::make_unique(*this); } virtual bool should_display() const { return true; } virtual void update(double secs); diff --git a/src/reactor/fuel/fuel_rod.hpp b/src/reactor/fuel/fuel_rod.hpp index 1e1b12d..e2eebb1 100644 --- a/src/reactor/fuel/fuel_rod.hpp +++ b/src/reactor/fuel/fuel_rod.hpp @@ -16,7 +16,6 @@ class fuel_rod : public sim::reactor::rod virtual bool has_sensors(val_t t) const { return true; } virtual const char* get_name() const { return "Fuel"; } - virtual rod* clone() const { return new fuel_rod(*this); }; virtual int get_id() const { return 1; } virtual glm::vec4 get_colour() const; @@ -24,6 +23,7 @@ public: fuel_rod(double fuel); + virtual std::unique_ptr clone() const { return std::make_unique(*this); } virtual bool should_display() const { return true; } virtual void update(double secs); }; diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index 73b2a22..71c0a39 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -8,7 +8,7 @@ using namespace sim::reactor; reactor::reactor(std::unique_ptr* rods, int w, int h, double cw, double ch) : cell_width(cw), cell_height(ch), width(w), height(h), size(w * h) { - this->rods = new std::unique_ptr[width * height]; + this->rods = std::make_unique[]>(width * height); for(int i = 0; i < size; i++) { @@ -19,14 +19,13 @@ reactor::reactor(std::unique_ptr* rods, int w, int h, double cw, double ch) reactor::reactor(reactor&& o) : cell_width(o.cell_width), cell_height(o.cell_height), width(o.width), height(o.height), size(o.size) { - rods = o.rods; + rods = std::move(o.rods); cursor = o.cursor; - o.rods = nullptr; } reactor::reactor(const reactor& o) : cell_width(o.cell_width), cell_height(o.cell_height), width(o.width), height(o.height), size(o.size) { - rods = new std::unique_ptr[width * height]; + this->rods = std::make_unique[]>(width * height); for(int i = 0; i < size; i++) { @@ -34,14 +33,6 @@ reactor::reactor(const reactor& o) : cell_width(o.cell_width), cell_height(o.cel } } -reactor::~reactor() -{ - if(rods != nullptr) - { - delete[] rods; - } -} - void reactor::update(double secs) { int rods_lookup[size]; diff --git a/src/reactor/reactor.hpp b/src/reactor/reactor.hpp index 55e7e38..7fed016 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -18,13 +18,12 @@ struct reactor const int height; const int size; - std::unique_ptr* rods; + std::unique_ptr[]> rods; int cursor = 0; reactor(std::unique_ptr* rods, int width, int height, double cell_width, double cell_height); reactor(const reactor& r); reactor(reactor&& r); - ~reactor(); void update(double secs); void update_selected(int v); diff --git a/src/reactor/rod.hpp b/src/reactor/rod.hpp index 755e896..284da37 100644 --- a/src/reactor/rod.hpp +++ b/src/reactor/rod.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace sim::reactor { @@ -21,12 +22,13 @@ public: N_FAST = 2 }; + virtual ~rod() {}; virtual void interact(rod* o, double secs); virtual void update(double secs) { } virtual void add(val_t type, double v); virtual double extract(val_t type, double s, double k, double o); virtual double get(val_t type) const; - virtual rod* clone() const { return new rod(*this); } + virtual std::unique_ptr clone() const { return std::make_unique(*this); } virtual glm::vec4 get_colour() const { return {0, 0, 0, 0}; } virtual int get_id() const { return 0; } diff --git a/src/system.cpp b/src/system.cpp index 7785f24..64b5797 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -33,37 +33,22 @@ system::system() " C C C C " }; - vessel = new reactor::coolant::vessel(8, 10, 300, sim::coolant::WATER); - reactor = new sim::reactor::reactor(sim::reactor::builder(19, 19, 0.25, 8, + vessel = std::make_unique(8, 10, 300, sim::coolant::WATER); + reactor = std::make_unique(sim::reactor::builder(19, 19, 0.25, 8, reactor::fuel::fuel_rod(0.5), - reactor::control::boron_rod(*vessel, 1), - *vessel, layout)); + reactor::control::boron_rod(*vessel.get(), 1), + *vessel.get(), layout)); - valve = new coolant::valve(*vessel, 1, 500); - pump = new coolant::pump(*vessel, 1e4, 15); - - scene.load_model("../assets/model", "scene_collisions.stl"); + valve = std::make_unique>(*vessel.get(), 1, 500); + pump = std::make_unique>(*vessel.get(), 1e4, 15); } system::system(system&& o) { - vessel = o.vessel; - reactor = o.reactor; - valve = o.valve; - pump = o.pump; - - o.vessel = nullptr; - o.reactor = nullptr; - o.valve = nullptr; - o.pump = nullptr; -} - -system::~system() -{ - if(vessel != nullptr) delete vessel; - if(reactor != nullptr) delete reactor; - if(valve != nullptr) delete valve; - if(pump != nullptr) delete pump; + vessel = std::move(o.vessel); + reactor = std::move(o.reactor); + valve = std::move(o.valve); + pump = std::move(o.pump); } void system::update(double dt) diff --git a/src/system.hpp b/src/system.hpp index 36b0f4a..148ea54 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -14,16 +14,15 @@ namespace sim struct system { - sim::reactor::reactor* reactor; - sim::reactor::coolant::vessel* vessel; - sim::coolant::valve* valve; - sim::coolant::pump* pump; + std::unique_ptr reactor; + std::unique_ptr vessel; + std::unique_ptr> valve; + std::unique_ptr> pump; sim::graphics::mesh scene; system(); system(system&& o); system(const system& o) = delete; - ~system(); void update(double dt); }; diff --git a/src/math.hpp b/src/util/math.hpp similarity index 100% rename from src/math.hpp rename to src/util/math.hpp From 8e690cc3d1a63dda860c3b2a05533ec416134228 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Tue, 30 Jan 2024 19:39:19 +1100 Subject: [PATCH 25/43] made interfaces actually semi functional! :) --- src/graphics/camera.cpp | 11 +++ src/graphics/camera.hpp | 4 +- src/graphics/input/focus.cpp | 82 +++++++++++++++++++++ src/graphics/input/focus.hpp | 30 ++++++++ src/graphics/input/keyboard.cpp | 14 ++++ src/graphics/input/mouse.cpp | 14 ++++ src/graphics/mesh/arrays.cpp | 10 +++ src/graphics/mesh/arrays.hpp | 5 +- src/graphics/mesh/mesh.cpp | 20 +++-- src/graphics/mesh/mesh.hpp | 1 + src/graphics/monitor/core.cpp | 126 ++++++++++++++++++++++++++++---- src/graphics/monitor/core.hpp | 2 + src/graphics/window.cpp | 22 ++++-- src/main.cpp | 2 + src/reactor/builder.cpp | 6 +- src/reactor/fuel/fuel_rod.cpp | 2 +- src/reactor/fuel/sample.hpp | 2 +- src/reactor/reactor.cpp | 13 +++- src/reactor/reactor.hpp | 2 +- 19 files changed, 324 insertions(+), 44 deletions(-) create mode 100644 src/graphics/input/focus.cpp create mode 100644 src/graphics/input/focus.hpp diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index 93f8aec..f8e2d21 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -36,6 +36,17 @@ void camera::move(double xoff, double yoff, double zoff) pos.z += zoff; } +glm::vec<3, double> camera::get_normal() +{ + glm::mat<3, 3, double> mat(camera_mat); + return glm::vec<3, double>(0, 0, -1) * mat; +} + +glm::vec<3, double> camera::get_pos() +{ + return pos; +} + void camera::update(const system& sys, double dt) { glm::vec<2, double> off(0, 0); diff --git a/src/graphics/camera.hpp b/src/graphics/camera.hpp index 7d08761..50b809a 100644 --- a/src/graphics/camera.hpp +++ b/src/graphics/camera.hpp @@ -2,7 +2,6 @@ #pragma once #include -#include #include "../system.hpp" @@ -10,6 +9,9 @@ namespace sim::graphics::camera { glm::mat4 get_matrix(); +glm::vec<3, double> get_normal(); +glm::vec<3, double> get_pos(); + void rotate(double pitch, double yaw); void move(double x, double y, double z); void update(const system& sys, double dt); diff --git a/src/graphics/input/focus.cpp b/src/graphics/input/focus.cpp new file mode 100644 index 0000000..cd0293c --- /dev/null +++ b/src/graphics/input/focus.cpp @@ -0,0 +1,82 @@ + +#include "focus.hpp" +#include "../camera.hpp" +#include "../../util/math.hpp" + +#include + +#include +#include + +using namespace sim::graphics; + +static std::unique_ptr state = nullptr; +static bool triggered = false; + +void focus::on_keypress(int key, int sc, int action, int mods) +{ + if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) + { + state = nullptr; + } + + if(state) + { + state->on_keypress(key, sc, action, mods); + } +} + +void focus::on_mouse_button(int button, int action, int mods) +{ + if(state) + { + state->on_mouse_button(button, action, mods); + } + + else if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) + { + triggered = true; + } +} + +void focus::on_cursor_pos(double x, double y) +{ + if(state) + { + state->on_cursor_pos(x, y); + } +} + +void focus::on_charcode(unsigned int c) +{ + if(state) + { + state->on_charcode(c); + } +} + +bool focus::is_focused() +{ + return (state != nullptr); +} + +void focus::clear() +{ + state = nullptr; +} + +void focus::set(std::unique_ptr f) +{ + state = std::move(f); +} + +bool focus::is_triggered() +{ + return triggered; +} + +void focus::clear_trigger() +{ + triggered = false; +} + diff --git a/src/graphics/input/focus.hpp b/src/graphics/input/focus.hpp new file mode 100644 index 0000000..1bd4af7 --- /dev/null +++ b/src/graphics/input/focus.hpp @@ -0,0 +1,30 @@ + +#pragma once + +#include + +namespace sim::graphics::focus +{ + +struct focus_t +{ + virtual ~focus_t() { } + virtual void on_keypress(int key, int sc, int action, int mods) { } + virtual void on_mouse_button(int button, int action, int mods) { } + virtual void on_cursor_pos(double x, double y) { } + virtual void on_charcode(unsigned int c) { } +}; + +bool is_triggered(); +void clear_trigger(); + +void clear(); +bool is_focused(); +void set(std::unique_ptr f); +void on_keypress(int key, int sc, int action, int mods); +void on_mouse_button(int button, int action, int mods); +void on_cursor_pos(double x, double y); +void on_charcode(unsigned int c); + +}; + diff --git a/src/graphics/input/keyboard.cpp b/src/graphics/input/keyboard.cpp index 09252cb..285a2a6 100644 --- a/src/graphics/input/keyboard.cpp +++ b/src/graphics/input/keyboard.cpp @@ -4,6 +4,7 @@ #include +#include "focus.hpp" #include "keyboard.hpp" #include "../window.hpp" #include "../resize.hpp" @@ -29,10 +30,22 @@ static void cb_keypress(GLFWwindow* win, int key, int sc, int action, int mods) { pressed[key] = false; } + + focus::on_keypress(key, sc, action, mods); +} + +static void cb_charcode(GLFWwindow* win, unsigned int code) +{ + focus::on_charcode(code); } bool keyboard::is_pressed(int key) { + if(focus::is_focused()) + { + return false; + } + auto it = pressed.find(key); if(it == pressed.end()) @@ -50,5 +63,6 @@ void keyboard::init() { GLFWwindow* win = window::get_window(); glfwSetKeyCallback(win, cb_keypress); + glfwSetCharCallback(win, cb_charcode); } diff --git a/src/graphics/input/mouse.cpp b/src/graphics/input/mouse.cpp index 96a4a6a..a850103 100644 --- a/src/graphics/input/mouse.cpp +++ b/src/graphics/input/mouse.cpp @@ -2,6 +2,7 @@ #include #include +#include "focus.hpp" #include "mouse.hpp" #include "../window.hpp" #include "../camera.hpp" @@ -12,12 +13,24 @@ static double xpos = 0, ypos = 0; static void cb_cursor_pos(GLFWwindow* win, double x, double y) { + focus::on_cursor_pos(x, y); + + if(focus::is_focused()) + { + return; + } + camera::rotate(x - xpos, y - ypos); xpos = x; ypos = y; } +void cb_mouse_button(GLFWwindow* window, int button, int action, int mods) +{ + focus::on_mouse_button(button, action, mods); +} + void mouse::get(double& x, double& y) { x = xpos; @@ -28,6 +41,7 @@ void mouse::init() { GLFWwindow* win = window::get_window(); glfwSetCursorPosCallback(win, cb_cursor_pos); + glfwSetMouseButtonCallback(win, cb_mouse_button); glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetCursorPos(win, 0, 0); } diff --git a/src/graphics/mesh/arrays.cpp b/src/graphics/mesh/arrays.cpp index 63023f3..4d6978f 100644 --- a/src/graphics/mesh/arrays.cpp +++ b/src/graphics/mesh/arrays.cpp @@ -34,3 +34,13 @@ void arrays::vertex_attrib_pointers() glEnableVertexAttribArray(3); } +glm::mat4 arrays::colour(glm::vec4 c) +{ + return glm::mat4({ + c.r, c.g, c.b, c.a, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }); +} + diff --git a/src/graphics/mesh/arrays.hpp b/src/graphics/mesh/arrays.hpp index 2e42132..1b6f41c 100644 --- a/src/graphics/mesh/arrays.hpp +++ b/src/graphics/mesh/arrays.hpp @@ -1,9 +1,7 @@ #pragma once -#include -#include -#include +#include namespace sim::graphics::arrays { @@ -17,6 +15,7 @@ struct vertex }; void vertex_attrib_pointers(); +glm::mat4 colour(glm::vec4 code); }; diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp index 7f60a12..725046f 100644 --- a/src/graphics/mesh/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -3,6 +3,7 @@ #include "arrays.hpp" #include "../shader.hpp" #include "../camera.hpp" +#include "../input/focus.hpp" #include "../../util/math.hpp" #include @@ -95,6 +96,11 @@ bool ray_intersects_triangle(vec3 ray_origin, return false; } +bool mesh::check_focus(double len) const +{ + return focus::is_triggered() && check_intersect(camera::get_pos(), camera::get_normal() * len); +} + bool mesh::check_intersect(vec3 pos, vec3 path) const { double l = glm::length(path); @@ -106,7 +112,7 @@ bool mesh::check_intersect(vec3 pos, vec3 path) const vec3 path_n = path / l; - for(unsigned int i = 0; i < indices.size(); i++) + for(unsigned int i = 0; i < indices.size(); i += 3) { vec3 v[3] = { vec3(this->vertices[indices[i]].pos), @@ -116,7 +122,7 @@ bool mesh::check_intersect(vec3 pos, vec3 path) const vec3 ipoint; vec3 normal = glm::normalize(glm::cross(v[1] - v[0], v[2] - v[0])); - double d = glm::dot(normal, path_n); + double d = glm::dot(normal, path); if(d >= 0) continue; @@ -124,7 +130,7 @@ bool mesh::check_intersect(vec3 pos, vec3 path) const continue; if(l < glm::length(ipoint - pos)) continue; - + return true; } @@ -194,8 +200,8 @@ vec3 mesh::calc_intersect(vec3 pos, vec3 path, vec3& normal_last) const i_found = i; } } -/* - for(unsigned int i = i_found - 1; i >= 0; i--) + + for(unsigned int i = 0; i < i_found; i += 3) { vec3 v[3] = { vec3(this->vertices[indices[i]].pos), @@ -205,7 +211,7 @@ vec3 mesh::calc_intersect(vec3 pos, vec3 path, vec3& normal_last) const calc_intercept_vert(v, pos, path, path_n, normal_last, l); } -*/ + return path; } @@ -224,8 +230,6 @@ mesh mesh::to_lines() const m.indices.push_back(indices[i + 2]); } - std::cout << "indices.size() = " << m.indices.size() << ", vertices.size() = " << m.vertices.size() << "\n"; - return m; } diff --git a/src/graphics/mesh/mesh.hpp b/src/graphics/mesh/mesh.hpp index 7c54f5b..9aea398 100644 --- a/src/graphics/mesh/mesh.hpp +++ b/src/graphics/mesh/mesh.hpp @@ -28,6 +28,7 @@ struct mesh void add(const mesh& o, glm::mat4 mat); mesh to_lines() const; + bool check_focus(double len) const; bool check_intersect(glm::vec<3, double> pos, glm::vec<3, double> path) const; glm::vec<3, double> calc_intersect(glm::vec<3, double> pos, glm::vec<3, double> path) const; glm::vec<3, double> calc_intersect(glm::vec<3, double> pos, glm::vec<3, double> path, glm::vec<3, double>& normal_last) const; diff --git a/src/graphics/monitor/core.cpp b/src/graphics/monitor/core.cpp index 522760f..04c9105 100644 --- a/src/graphics/monitor/core.cpp +++ b/src/graphics/monitor/core.cpp @@ -4,12 +4,89 @@ #include "core.hpp" #include "../locations.hpp" +#include "../input/focus.hpp" +#include "../mesh/arrays.hpp" #include + +#include #include +using namespace sim::graphics; using namespace sim::graphics::monitor; +struct core_focus_t : public focus::focus_t +{ + sim::system* sys; + + core_focus_t(sim::system& sys) + { + this->sys = &sys; + } + + void on_keypress(int key, int sc, int action, int mods) + { + if(action != GLFW_PRESS) + { + return; + } + + } + + void on_mouse_button(int button, int action, int mods) + { + + } + + void on_cursor_pos(double x, double y) + { + + } + + void set_all(bool state) + { + for(int i = 0; i < sys->reactor->rods.size(); i++) + { + sim::reactor::rod* r = sys->reactor->rods[i].get(); + + if(r->should_select() && (r->is_selected() != state)) + { + r->toggle_selected(); + } + } + } + + void toggle_auto() + { + //TODO + } + + void on_charcode(unsigned int c) + { + switch(c) + { + case 'a': case 'A': + sys->reactor->move_cursor(-1); + break; + case 'd': case 'D': + sys->reactor->move_cursor(1); + break; + case 's': case 'S': + sys->reactor->toggle_selected(); + break; + case 'w': case 'W': + toggle_auto(); + break; + case 'q': case 'Q': + set_all(false); + break; + case 'e': case 'E': + set_all(true); + break; + } + } +}; + core::core() { @@ -18,12 +95,7 @@ core::core() void core::init() { mesh1.model_matrix = locations::monitors[2]; - mesh1.colour_matrix = { - 1, 1, 1, 1, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0 - }; + mesh1.colour_matrix = arrays::colour({1, 1, 1, 1}); sim::graphics::mesh rmesh1, rmesh2; @@ -36,6 +108,16 @@ void core::init() rmesh2.load_model("../assets/model/", "reactor_core_interface_cell.stl"); mesh2.bind(); mesh2.set(rmesh2, GL_STATIC_DRAW); + + mesh_click.load_model("../assets/model/", "reactor_core_input.stl"); +} + +void core::update(sim::system& sys) +{ + if(mesh_click.check_focus(2)) + { + focus::set(std::make_unique(sys)); + } } void core::render(sim::system& sys) @@ -44,7 +126,9 @@ void core::render(sim::system& sys) double sx = 0.5 - (sys.reactor->width - 1) * step / 2.0; double sy = 0.5 - (sys.reactor->height - 1) * step / 2.0; - glm::mat4 mat_scale = glm::scale(glm::mat4(1), glm::vec3(step * 0.5, step * 0.5, 1)); + glm::mat4 mat_scale = glm::scale(glm::mat4(1), glm::vec3(step * 0.4, step * 0.4, 1)); + glm::mat4 mat_select = glm::translate(glm::mat4(1), glm::vec3(-0.8, -0.8, -0.001)) * glm::scale(glm::mat4(1), glm::vec3(0.5, 0.5, 1)); + glm::mat4 mat_cursor = glm::translate(glm::mat4(1), glm::vec3(-0.8, 0.8, -0.001)) * glm::scale(glm::mat4(1), glm::vec3(0.5, 0.5, 1)); mesh1.bind(); mesh1.uniform(); @@ -61,21 +145,33 @@ void core::render(sim::system& sys) reactor::rod* r = sys.reactor->rods[i].get(); glm::vec4 colour = r->get_colour(); - if(!r->should_display() || colour[3] == 0) + if(colour[3] == 0) { continue; } - mesh2.model_matrix = mesh1.model_matrix * glm::translate(glm::mat4(1), glm::vec3(ox, oy, 0)) * mat_scale; - mesh2.colour_matrix = glm::mat4({ - colour[0], colour[1], colour[2], colour[3], - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0 - }); + glm::mat4 mat = mesh1.model_matrix * glm::translate(glm::mat4(1), glm::vec3(ox, oy, 0)) * mat_scale; + mesh2.model_matrix = mat; + mesh2.colour_matrix = arrays::colour(colour); mesh2.uniform(); mesh2.render(); + + if(sys.reactor->cursor == i && r->should_select()) + { + mesh2.model_matrix = mat * mat_cursor; + mesh2.colour_matrix = arrays::colour({1, 0, 0, 1}); + mesh2.uniform(); + mesh2.render(); + } + + if(r->is_selected()) + { + mesh2.model_matrix = mat * mat_select; + mesh2.colour_matrix = arrays::colour({1, 1, 0, 1}); + mesh2.uniform(); + mesh2.render(); + } } } diff --git a/src/graphics/monitor/core.hpp b/src/graphics/monitor/core.hpp index 09908f2..44ca65f 100644 --- a/src/graphics/monitor/core.hpp +++ b/src/graphics/monitor/core.hpp @@ -9,12 +9,14 @@ namespace sim::graphics::monitor class core { + sim::graphics::mesh mesh_click; sim::graphics::glmesh mesh1, mesh2; public: core(); void init(); + void update(sim::system& sys); void render(sim::system& sys); }; diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 15f0cfc..325815e 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -28,7 +28,7 @@ using namespace sim::graphics; static GLFWwindow* win; static bool win_should_close = false; -static glmesh MeshScene, MeshCollisionScene; +static glmesh MeshScene, MeshDebug; static monitor::vessel MonitorVessel; static monitor::core MonitorCore; @@ -101,9 +101,13 @@ void window::create(system& sys) MeshScene.bind(); MeshScene.set(sys.scene, GL_STATIC_DRAW); +// sys.scene.load_model("../assets/model", "reactor_core_input.stl"); +// MeshDebug.bind(); +// MeshDebug.set(sys.scene, GL_STATIC_DRAW); + sys.scene.load_model("../assets/model", "scene_collisions.stl"); - MeshCollisionScene.bind(); - MeshCollisionScene.set(sys.scene.to_lines(), GL_STATIC_DRAW); +// MeshCollisionScene.bind(); +// MeshCollisionScene.set(sys.scene.to_lines(), GL_STATIC_DRAW); MonitorCore.init(); MonitorVessel.init(); @@ -114,6 +118,9 @@ void window::create(system& sys) void window::loop(sim::system& sys) { + glfwPollEvents(); + + MonitorCore.update(sys); MonitorVessel.update(sys); glm::mat4 mat_projection = glm::perspective(glm::radians(80.0f), resize::get_aspect(), 0.01f, 20.f); @@ -121,20 +128,19 @@ void window::loop(sim::system& sys) glClearColor(0, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + +// MeshDebug.bind(); +// MeshDebug.uniform(); +// MeshDebug.render(); MeshScene.bind(); MeshScene.uniform(); MeshScene.render(); - MeshCollisionScene.bind(); - MeshCollisionScene.uniform(); - MeshCollisionScene.render(GL_LINES); - MonitorCore.render(sys); MonitorVessel.render(); glfwSwapBuffers(win); - glfwPollEvents(); } bool window::should_close() diff --git a/src/main.cpp b/src/main.cpp index d7f2a2d..bec1ecd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,7 @@ #include "coolant/pump.hpp" #include "graphics/mesh/mesh.hpp" +#include "graphics/input/focus.hpp" #include "graphics/window.hpp" #include "graphics/camera.hpp" @@ -43,6 +44,7 @@ int main() sys.update(dt); graphics::camera::update(sys, dt); graphics::window::loop(sys); + graphics::focus::clear_trigger(); } graphics::window::destroy(); diff --git a/src/reactor/builder.cpp b/src/reactor/builder.cpp index 87ad129..2c14fee 100644 --- a/src/reactor/builder.cpp +++ b/src/reactor/builder.cpp @@ -20,10 +20,10 @@ sim::reactor::reactor sim::reactor::builder(const int W, const int H, const doub switch(c) { case 'F': - r = fr.clone(); + r = std::make_unique(fr); break; case 'C': - r = br.clone(); + r = std::make_unique(br); break; case 'G': r = std::make_unique(); @@ -34,7 +34,7 @@ sim::reactor::reactor sim::reactor::builder(const int W, const int H, const doub case 'P': r = std::make_unique(v); break; - case ' ': + default: r = std::make_unique(); break; } diff --git a/src/reactor/fuel/fuel_rod.cpp b/src/reactor/fuel/fuel_rod.cpp index 6951be9..954a8dd 100644 --- a/src/reactor/fuel/fuel_rod.cpp +++ b/src/reactor/fuel/fuel_rod.cpp @@ -21,7 +21,7 @@ void fuel_rod::display(std::ostream& o) const o << "Fuel: " << (s.get_fuel() * mol) << " / " << (s.get_mass() * mol) << " mol\n"; o << "Efficiency: " << (s.get_efficiency() * 100) << " %\n"; - o << "Output: " << (s.get_energy() * mol * energy_density) << " W/s\n"; + o << "Output: " << (s.get_energy() * mol * energy_density) << " W\n"; o << "Iodine: " << (s.get_i_135() * mol) << " mol\n"; o << "Xenon: " << (s.get_xe_135() * mol) << " mol\n"; } diff --git a/src/reactor/fuel/sample.hpp b/src/reactor/fuel/sample.hpp index 4579c0c..bad2c8c 100644 --- a/src/reactor/fuel/sample.hpp +++ b/src/reactor/fuel/sample.hpp @@ -21,7 +21,7 @@ class sample double te_135 = 0; double mass = 0; - double energy = 0; // W/s + double energy = 0; // W double fast_neutrons = 0; // mol/s double slow_neutrons = 0; // mol/s double efficiency = 0; diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index 71c0a39..00def97 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -8,7 +8,7 @@ using namespace sim::reactor; reactor::reactor(std::unique_ptr* rods, int w, int h, double cw, double ch) : cell_width(cw), cell_height(ch), width(w), height(h), size(w * h) { - this->rods = std::make_unique[]>(width * height); + this->rods = std::vector>(w * h); for(int i = 0; i < size; i++) { @@ -21,15 +21,22 @@ reactor::reactor(reactor&& o) : cell_width(o.cell_width), cell_height(o.cell_hei { rods = std::move(o.rods); cursor = o.cursor; + + for(int i = 0; i < size; i++) + { + rods[i]->reactor = this; + } } reactor::reactor(const reactor& o) : cell_width(o.cell_width), cell_height(o.cell_height), width(o.width), height(o.height), size(o.size) { - this->rods = std::make_unique[]>(width * height); + rods = std::vector>(width * height); + cursor = o.cursor; for(int i = 0; i < size; i++) { - this->rods[i] = std::unique_ptr(o.rods[i]->clone()); + rods[i] = std::unique_ptr(o.rods[i]->clone()); + rods[i]->reactor = this; } } diff --git a/src/reactor/reactor.hpp b/src/reactor/reactor.hpp index 7fed016..7ebc894 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -18,7 +18,7 @@ struct reactor const int height; const int size; - std::unique_ptr[]> rods; + std::vector> rods; int cursor = 0; reactor(std::unique_ptr* rods, int width, int height, double cell_width, double cell_height); From 4d9e3d8fa9b0ba9a2e76e02f40c165d6df1e5e36 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 31 Jan 2024 00:09:46 +1100 Subject: [PATCH 26/43] git lfs --- .gitattributes | 7 ++++--- .gitignore | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitattributes b/.gitattributes index df209d7..10723d3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,8 @@ -* !text !filter !merge !diff -*.blend filter=lfs diff=lfs merge=lfs -text -*.glb filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text +*.stl filter=lfs diff=lfs merge=lfs -text +*.glb filter=lfs diff=lfs merge=lfs -text +*.blend filter=lfs diff=lfs merge=lfs -text +* !text !filter !merge !diff *.obj filter=lfs diff=lfs merge=lfs -text *.fbx filter=lfs diff=lfs merge=lfs -text *.bin filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 828958c..e27ce7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ build *.blend1 -assets/scene-baked.glb From 585642caf06ddcc7dca89b2135c498882eed99c6 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 31 Jan 2024 00:12:14 +1100 Subject: [PATCH 27/43] git lfs files --- assets/font/DroidSans.ttf | Bin 0 -> 190776 bytes assets/model/reactor_core_input.stl | Bin 0 -> 584 bytes assets/model/reactor_core_interface_cell.scad | 3 + assets/model/reactor_core_interface_cell.stl | Bin 0 -> 684 bytes .../model/reactor_core_interface_circle.scad | 10 +++ .../model/reactor_core_interface_circle.stl | Bin 0 -> 102484 bytes assets/model/scene_collisions.stl | Bin 0 -> 26884 bytes assets/scene-baked.glb | 3 + assets/shader/main.fsh | 20 +++++ assets/shader/main.vsh | 28 ++++++ assets/unbaked/AllPlants/AllPlants.mtl | 82 ++++++++++++++++++ assets/unbaked/AllPlants/AllPlants.obj | 3 + assets/unbaked/AllPlants/PlantsLargeUV.png | 3 + assets/unbaked/AllPlants/PlantsSmallUV.png | 3 + assets/unbaked/scene.blend | 3 + assets/unbaked/scene.stl | Bin 0 -> 26884 bytes .../scene/smooth+white+tile-1024x1024.png | 3 + 17 files changed, 161 insertions(+) create mode 100644 assets/font/DroidSans.ttf create mode 100644 assets/model/reactor_core_input.stl create mode 100644 assets/model/reactor_core_interface_cell.scad create mode 100644 assets/model/reactor_core_interface_cell.stl create mode 100644 assets/model/reactor_core_interface_circle.scad create mode 100644 assets/model/reactor_core_interface_circle.stl create mode 100644 assets/model/scene_collisions.stl create mode 100644 assets/scene-baked.glb create mode 100644 assets/shader/main.fsh create mode 100644 assets/shader/main.vsh create mode 100644 assets/unbaked/AllPlants/AllPlants.mtl create mode 100644 assets/unbaked/AllPlants/AllPlants.obj create mode 100644 assets/unbaked/AllPlants/PlantsLargeUV.png create mode 100644 assets/unbaked/AllPlants/PlantsSmallUV.png create mode 100644 assets/unbaked/scene.blend create mode 100644 assets/unbaked/scene.stl create mode 100644 assets/unbaked/scene/smooth+white+tile-1024x1024.png diff --git a/assets/font/DroidSans.ttf b/assets/font/DroidSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ad1efca88aed8d9e2d179f27dd713e2a1562fe5f GIT binary patch literal 190776 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 literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_input.stl b/assets/model/reactor_core_input.stl new file mode 100644 index 0000000000000000000000000000000000000000..7d86b9999f16c38872d977dcb044beb2e898c7aa GIT binary patch literal 584 zcmZ>*D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIaDg>7*xN8DYzHx!7#tiJ7#cv> z9>E5QIXIjhzyJW3qH{k0 literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_interface_cell.scad b/assets/model/reactor_core_interface_cell.scad new file mode 100644 index 0000000..317d259 --- /dev/null +++ b/assets/model/reactor_core_interface_cell.scad @@ -0,0 +1,3 @@ + +linear_extrude(0.0001) +square([1, 1], center = true); diff --git a/assets/model/reactor_core_interface_cell.stl b/assets/model/reactor_core_interface_cell.stl new file mode 100644 index 0000000000000000000000000000000000000000..3d05d78994b68c767ac640daf795cfb121e84cd4 GIT binary patch literal 684 zcmbu5I}XA?3`Dm^^l3RpI)p-tC~}3Qi-05Sttfp4Zp4n`NoJK$VQJ-Ad!F}me~pj6 zyIfDV=P*u(^lu!i0ols-Toy6{Sv7i9>=JY#Q!i9T#n5TqpEk6G_Fm|kKt~Dc4dFKe z>zx>4CG!l|QTMb<=v<|f-=8>KSO_It&*iidIb2Hoeg|C^6vm;Z1U=}j=P2P?ewTDw YUJVp3EQAuS=W>}(2VLAF>+i-HUraPs9{>OV literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_interface_circle.scad b/assets/model/reactor_core_interface_circle.scad new file mode 100644 index 0000000..4bdc405 --- /dev/null +++ b/assets/model/reactor_core_interface_circle.scad @@ -0,0 +1,10 @@ + +$fn = 256; + +linear_extrude(0.0001) +translate([0.5, 0.5]) +difference() +{ + circle(d = 0.81); + circle(d = 0.8); +} diff --git a/assets/model/reactor_core_interface_circle.stl b/assets/model/reactor_core_interface_circle.stl new file mode 100644 index 0000000000000000000000000000000000000000..8193953839cc2c43b6bb452541dc208981b64124 GIT binary patch literal 102484 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 literal 0 HcmV?d00001 diff --git a/assets/model/scene_collisions.stl b/assets/model/scene_collisions.stl new file mode 100644 index 0000000000000000000000000000000000000000..eee4323203c49391f716151ddc88e843e89e2502 GIT binary patch literal 26884 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 literal 0 HcmV?d00001 diff --git a/assets/scene-baked.glb b/assets/scene-baked.glb new file mode 100644 index 0000000..1c6cb0b --- /dev/null +++ b/assets/scene-baked.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e95d9c35819ae80c6a1bffd98b5127e905b18a3dca9bf7ce689d3caab487ca4c +size 66429308 diff --git a/assets/shader/main.fsh b/assets/shader/main.fsh new file mode 100644 index 0000000..13e4a90 --- /dev/null +++ b/assets/shader/main.fsh @@ -0,0 +1,20 @@ + +#version 460 core +#extension GL_ARB_bindless_texture : require + +in float brightness; +in flat sampler2D tex; +in vec2 texPos; + +out vec4 FragColour; + +uniform mat4 tex_mat; + +void main() +{ + vec4 texdata = texture2D(tex, texPos); + FragColour = tex_mat * texdata * vec4(vec3(brightness), 1); + + if(FragColour.a == 0) discard; +} + diff --git a/assets/shader/main.vsh b/assets/shader/main.vsh new file mode 100644 index 0000000..fbe8a82 --- /dev/null +++ b/assets/shader/main.vsh @@ -0,0 +1,28 @@ + +#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 vec4 aPos; +layout (location = 3) in vec3 aNormal; + +uniform mat4 model; +uniform mat4 projection; + +out float brightness; +out flat sampler2D tex; +out vec2 texPos; + +void main() +{ + vec4 pos = model * aPos; + vec3 cNormal = vec3(0.f, 0.f, 1.f) * mat3(model); + + brightness = dot(normalize(aNormal), normalize(cNormal)) * 0.25f + 0.75f; + + gl_Position = projection * pos; + texPos = aTexPos; + tex = aTex; +} + diff --git a/assets/unbaked/AllPlants/AllPlants.mtl b/assets/unbaked/AllPlants/AllPlants.mtl new file mode 100644 index 0000000..58727c5 --- /dev/null +++ b/assets/unbaked/AllPlants/AllPlants.mtl @@ -0,0 +1,82 @@ +# Blender 4.0.2 MTL File: 'None' +# www.blender.org + +newmtl M_ClayPot1 +Ns 380.250000 +Ka 1.000000 1.000000 1.000000 +Ks 0.370000 0.370000 0.370000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd PlantsLargeUV.png + +newmtl M_ClayPot2 +Ns 380.250000 +Ka 1.000000 1.000000 1.000000 +Ks 0.370000 0.370000 0.370000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd PlantsSmallUV.png + +newmtl M_Dirt +Ns 36.000000 +Ka 1.000000 1.000000 1.000000 +Ks 0.050000 0.050000 0.050000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd PlantsLargeUV.png + +newmtl M_Dirt2 +Ns 36.000000 +Ka 1.000000 1.000000 1.000000 +Ks 0.050000 0.050000 0.050000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd PlantsSmallUV.png + +newmtl M_GrassyLeaf +Ns 302.760040 +Ka 1.000000 1.000000 1.000000 +Ks 0.250000 0.250000 0.250000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd PlantsLargeUV.png + +newmtl M_IvyLeaf +Ns 324.000031 +Ka 1.000000 1.000000 1.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd PlantsSmallUV.png + +newmtl M_LargeLeaf +Ns 492.839996 +Ka 1.000000 1.000000 1.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd PlantsLargeUV.png + +newmtl M_SmallLeaf +Ns 380.250000 +Ka 1.000000 1.000000 1.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd PlantsSmallUV.png diff --git a/assets/unbaked/AllPlants/AllPlants.obj b/assets/unbaked/AllPlants/AllPlants.obj new file mode 100644 index 0000000..cea0718 --- /dev/null +++ b/assets/unbaked/AllPlants/AllPlants.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:321768633e17e4ca73d51c0d85851e0ccbce17fef10f7d87a5adb97e74d92325 +size 50441976 diff --git a/assets/unbaked/AllPlants/PlantsLargeUV.png b/assets/unbaked/AllPlants/PlantsLargeUV.png new file mode 100644 index 0000000..1e092ad --- /dev/null +++ b/assets/unbaked/AllPlants/PlantsLargeUV.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c7850bc877ead535a0b105ab513514f224eac71f6b5c5121b50f6125a0c93d8 +size 3748733 diff --git a/assets/unbaked/AllPlants/PlantsSmallUV.png b/assets/unbaked/AllPlants/PlantsSmallUV.png new file mode 100644 index 0000000..b3939a4 --- /dev/null +++ b/assets/unbaked/AllPlants/PlantsSmallUV.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94fbff1e4300b9c7cb9d8ea53fb990a5b556aee0cd1fd18be6e24488998e3862 +size 2943225 diff --git a/assets/unbaked/scene.blend b/assets/unbaked/scene.blend new file mode 100644 index 0000000..9e7ba4a --- /dev/null +++ b/assets/unbaked/scene.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe3f5df6086f9ddfe253116efadb3dbcbbca826b5cfcb3a320dc63c7e85b6ff6 +size 79959028 diff --git a/assets/unbaked/scene.stl b/assets/unbaked/scene.stl new file mode 100644 index 0000000000000000000000000000000000000000..eee4323203c49391f716151ddc88e843e89e2502 GIT binary patch literal 26884 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 literal 0 HcmV?d00001 diff --git a/assets/unbaked/scene/smooth+white+tile-1024x1024.png b/assets/unbaked/scene/smooth+white+tile-1024x1024.png new file mode 100644 index 0000000..d1c72c3 --- /dev/null +++ b/assets/unbaked/scene/smooth+white+tile-1024x1024.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1559e7c9919a98717fbe68e84520e20cd75ab60374ffa7c0393a589e979f17e7 +size 220014 From e51b4f5c80ce584879578507e6f172068d231168 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 31 Jan 2024 00:17:36 +1100 Subject: [PATCH 28/43] improved readme --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index bb6fc2b..dbf7293 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,22 @@ +# How to build + +This is built using CMake. You will also need the required libraries to build. + +``` +mkdir build +cd build +cmake .. +make +``` + +# Required libraries +- GLEW +- GLFW +- OpenGL +- FreeType +- AssImp + # Credits - [Potted House Plants](https://skfb.ly/opQN8) by FacultyManBruce is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). From 9c677cc32f645fe2ccce8bbcc2d89ee1a6c35868 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 31 Jan 2024 00:23:44 +1100 Subject: [PATCH 29/43] added improvements --- README.md | 1 + src/graphics/camera.cpp | 6 +-- src/graphics/camera.hpp | 2 +- src/graphics/input/focus.cpp | 15 ++++--- src/graphics/input/focus.hpp | 6 +-- src/graphics/input/mouse.cpp | 9 ++-- src/graphics/monitor/core.cpp | 80 +++++++++++++++++---------------- src/graphics/monitor/core.hpp | 4 +- src/graphics/monitor/vessel.cpp | 12 ++++- src/graphics/monitor/vessel.hpp | 2 +- src/graphics/window.cpp | 13 +++--- src/graphics/window.hpp | 4 +- src/main.cpp | 11 +++-- src/reactor/reactor.cpp | 30 ++++++++----- src/reactor/reactor.hpp | 5 ++- src/reactor/rod.cpp | 24 ---------- src/reactor/rod.hpp | 10 +---- src/system.cpp | 2 + src/system.hpp | 2 + 19 files changed, 118 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index dbf7293..7ce45d2 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ # How to build This is built using CMake. You will also need the required libraries to build. +This project currently only works on Linux. ``` mkdir build diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp index f8e2d21..750d637 100644 --- a/src/graphics/camera.cpp +++ b/src/graphics/camera.cpp @@ -47,7 +47,7 @@ glm::vec<3, double> camera::get_pos() return pos; } -void camera::update(const system& sys, double dt) +void camera::update(double dt) { glm::vec<2, double> off(0, 0); double m = 30; @@ -89,8 +89,8 @@ void camera::update(const system& sys, double dt) glm::vec<3, double> normal_last(0); glm::vec<3, double> velocity2; - velocity2 = sys.scene.calc_intersect(pos, velocity * dt, normal_last); - velocity2 = sys.scene.calc_intersect(pos + glm::vec<3, double>(0, 0, -1.5), velocity2, normal_last) / dt; + velocity2 = system::active.scene.calc_intersect(pos, velocity * dt, normal_last); + velocity2 = system::active.scene.calc_intersect(pos + glm::vec<3, double>(0, 0, -1.5), velocity2, normal_last) / dt; pos += velocity2 * dt; on_ground = ((velocity * dt / dt).z != velocity2.z); diff --git a/src/graphics/camera.hpp b/src/graphics/camera.hpp index 50b809a..fb9a1a4 100644 --- a/src/graphics/camera.hpp +++ b/src/graphics/camera.hpp @@ -14,7 +14,7 @@ glm::vec<3, double> get_pos(); void rotate(double pitch, double yaw); void move(double x, double y, double z); -void update(const system& sys, double dt); +void update(double dt); }; diff --git a/src/graphics/input/focus.cpp b/src/graphics/input/focus.cpp index cd0293c..334ab2a 100644 --- a/src/graphics/input/focus.cpp +++ b/src/graphics/input/focus.cpp @@ -55,6 +55,16 @@ void focus::on_charcode(unsigned int c) } } +void focus::update() +{ + triggered = false; + + if(state) + { + state->update(); + } +} + bool focus::is_focused() { return (state != nullptr); @@ -75,8 +85,3 @@ bool focus::is_triggered() return triggered; } -void focus::clear_trigger() -{ - triggered = false; -} - diff --git a/src/graphics/input/focus.hpp b/src/graphics/input/focus.hpp index 1bd4af7..f5ce407 100644 --- a/src/graphics/input/focus.hpp +++ b/src/graphics/input/focus.hpp @@ -13,18 +13,18 @@ struct focus_t virtual void on_mouse_button(int button, int action, int mods) { } virtual void on_cursor_pos(double x, double y) { } virtual void on_charcode(unsigned int c) { } + virtual void update() { } }; -bool is_triggered(); -void clear_trigger(); - void clear(); bool is_focused(); +bool is_triggered(); void set(std::unique_ptr f); void on_keypress(int key, int sc, int action, int mods); void on_mouse_button(int button, int action, int mods); void on_cursor_pos(double x, double y); void on_charcode(unsigned int c); +void update(); }; diff --git a/src/graphics/input/mouse.cpp b/src/graphics/input/mouse.cpp index a850103..2c7059b 100644 --- a/src/graphics/input/mouse.cpp +++ b/src/graphics/input/mouse.cpp @@ -13,14 +13,15 @@ static double xpos = 0, ypos = 0; static void cb_cursor_pos(GLFWwindow* win, double x, double y) { - focus::on_cursor_pos(x, y); - if(focus::is_focused()) { - return; + focus::on_cursor_pos(x - xpos, y - ypos); } - camera::rotate(x - xpos, y - ypos); + else + { + camera::rotate(x - xpos, y - ypos); + } xpos = x; ypos = y; diff --git a/src/graphics/monitor/core.cpp b/src/graphics/monitor/core.cpp index 04c9105..f119f83 100644 --- a/src/graphics/monitor/core.cpp +++ b/src/graphics/monitor/core.cpp @@ -17,37 +17,16 @@ using namespace sim::graphics::monitor; struct core_focus_t : public focus::focus_t { - sim::system* sys; - - core_focus_t(sim::system& sys) + virtual void on_cursor_pos(double x, double y) { - this->sys = &sys; - } - - void on_keypress(int key, int sc, int action, int mods) - { - if(action != GLFW_PRESS) - { - return; - } - - } - - void on_mouse_button(int button, int action, int mods) - { - - } - - void on_cursor_pos(double x, double y) - { - + sim::system::active.reactor->rod_speed -= y * 1e-6; } void set_all(bool state) { - for(int i = 0; i < sys->reactor->rods.size(); i++) + for(int i = 0; i < sim::system::active.reactor->rods.size(); i++) { - sim::reactor::rod* r = sys->reactor->rods[i].get(); + sim::reactor::rod* r = sim::system::active.reactor->rods[i].get(); if(r->should_select() && (r->is_selected() != state)) { @@ -61,28 +40,51 @@ struct core_focus_t : public focus::focus_t //TODO } - void on_charcode(unsigned int c) + virtual void on_charcode(unsigned int c) { + sim::system& sys = sim::system::active; + switch(c) { case 'a': case 'A': - sys->reactor->move_cursor(-1); + sys.reactor->move_cursor(-1); break; case 'd': case 'D': - sys->reactor->move_cursor(1); - break; - case 's': case 'S': - sys->reactor->toggle_selected(); + sys.reactor->move_cursor(1); break; case 'w': case 'W': + sys.reactor->move_cursor(-sim::system::active.reactor->height); + break; + case 's': case 'S': + sys.reactor->move_cursor(sim::system::active.reactor->height); + break; + } + } + + virtual void on_keypress(int key, int sc, int action, int mods) + { + if(action != GLFW_PRESS) + { + return; + } + + switch(key) + { + case GLFW_KEY_Z: + sim::system::active.reactor->toggle_selected(); + break; + case GLFW_KEY_C: toggle_auto(); break; - case 'q': case 'Q': - set_all(false); + case GLFW_KEY_X: + sim::system::active.reactor->rod_speed = 0; break; - case 'e': case 'E': + case GLFW_KEY_Q: set_all(true); break; + case GLFW_KEY_E: + set_all(false); + break; } } }; @@ -112,16 +114,18 @@ void core::init() mesh_click.load_model("../assets/model/", "reactor_core_input.stl"); } -void core::update(sim::system& sys) +void core::update() { if(mesh_click.check_focus(2)) { - focus::set(std::make_unique(sys)); + focus::set(std::make_unique()); } } -void core::render(sim::system& sys) +void core::render() { + sim::system& sys = sim::system::active; + double step = 1 / (sys.vessel->diameter / sys.reactor->cell_width * 0.8); double sx = 0.5 - (sys.reactor->width - 1) * step / 2.0; double sy = 0.5 - (sys.reactor->height - 1) * step / 2.0; @@ -157,7 +161,7 @@ void core::render(sim::system& sys) mesh2.uniform(); mesh2.render(); - if(sys.reactor->cursor == i && r->should_select()) + if(sys.reactor->cursor == i) { mesh2.model_matrix = mat * mat_cursor; mesh2.colour_matrix = arrays::colour({1, 0, 0, 1}); diff --git a/src/graphics/monitor/core.hpp b/src/graphics/monitor/core.hpp index 44ca65f..ccd71a3 100644 --- a/src/graphics/monitor/core.hpp +++ b/src/graphics/monitor/core.hpp @@ -16,8 +16,8 @@ public: core(); void init(); - void update(sim::system& sys); - void render(sim::system& sys); + void update(); + void render(); }; }; diff --git a/src/graphics/monitor/vessel.cpp b/src/graphics/monitor/vessel.cpp index 8c985fe..d443e44 100644 --- a/src/graphics/monitor/vessel.cpp +++ b/src/graphics/monitor/vessel.cpp @@ -41,20 +41,27 @@ void vessel::init() ss << "Reactor Core\n\n"; ss << "Temperature\nMin\nMax\n\n"; ss << "Neutron Flux\nSlow\nFast\n\n"; - ss << "Control Rods\nMin\nMax\n\n"; + ss << "Control Rods\nMin\nMax\nSpeed\n"; rmesh.load_text(ss.str().c_str(), 0.04); mesh1.bind(); mesh1.set(rmesh, GL_STATIC_DRAW); } +static double show(double v, double m) +{ + return std::round(v * m) / m; +} + static double show(double v) { return std::round(v * 1e3) * 1e-3; } -void vessel::update(sim::system& sys) +void vessel::update() { + sim::system& sys = sim::system::active; + std::stringstream ss; sim::graphics::mesh rmesh; @@ -98,6 +105,7 @@ void vessel::update(sim::system& sys) ss << sys.reactor->get_total(sim::reactor::rod::val_t::N_FAST) << " mol\n\n\n"; ss << show( crod_min * 100 ) << " %\n"; ss << show( crod_max * 100 ) << " %\n"; + ss << show( sys.reactor->rod_speed * 100, 1e-5 ) << " %/s\n"; rmesh.load_text(ss.str().c_str(), 0.04); mesh2.bind(); diff --git a/src/graphics/monitor/vessel.hpp b/src/graphics/monitor/vessel.hpp index 016bd08..fb5e3a4 100644 --- a/src/graphics/monitor/vessel.hpp +++ b/src/graphics/monitor/vessel.hpp @@ -15,7 +15,7 @@ public: vessel(); void init(); - void update(sim::system& sys); + void update(); void render(); }; diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 325815e..9a5c4b5 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -18,7 +18,6 @@ #include "shader.hpp" #include "mesh/font.hpp" #include "locations.hpp" -#include "../system.hpp" #include "monitor/vessel.hpp" #include "monitor/core.hpp" #include "mesh/texture.hpp" @@ -40,7 +39,7 @@ void GLAPIENTRY cb_debug_message(GLenum source, GLenum type, GLuint id, GLenum s } } -void window::create(system& sys) +void window::create() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); @@ -97,6 +96,8 @@ void window::create(system& sys) shader::init_program(); + sim::system& sys = sim::system::active; + sys.scene.load_model("../assets", "scene-baked.glb"); MeshScene.bind(); MeshScene.set(sys.scene, GL_STATIC_DRAW); @@ -116,12 +117,12 @@ void window::create(system& sys) glViewport(0, 0, 800, 600); } -void window::loop(sim::system& sys) +void window::loop() { glfwPollEvents(); - MonitorCore.update(sys); - MonitorVessel.update(sys); + MonitorCore.update(); + MonitorVessel.update(); glm::mat4 mat_projection = glm::perspective(glm::radians(80.0f), resize::get_aspect(), 0.01f, 20.f); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); @@ -137,7 +138,7 @@ void window::loop(sim::system& sys) MeshScene.uniform(); MeshScene.render(); - MonitorCore.render(sys); + MonitorCore.render(); MonitorVessel.render(); glfwSwapBuffers(win); diff --git a/src/graphics/window.hpp b/src/graphics/window.hpp index f9222d8..58c934d 100644 --- a/src/graphics/window.hpp +++ b/src/graphics/window.hpp @@ -9,8 +9,8 @@ namespace sim::graphics::window { bool should_close(); -void create(sim::system& sys); -void loop(sim::system& sys); +void create(); +void loop(); void destroy(); void close(); diff --git a/src/main.cpp b/src/main.cpp index bec1ecd..7665d81 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,8 +29,7 @@ unsigned long get_now() int main() { - sim::system sys; - graphics::window::create(sys); + graphics::window::create(); long clock = get_now(); @@ -41,10 +40,10 @@ int main() double dt = (double)passed / 1e6; clock += passed; - sys.update(dt); - graphics::camera::update(sys, dt); - graphics::window::loop(sys); - graphics::focus::clear_trigger(); + sim::system::active.update(dt); + graphics::camera::update(dt); + graphics::window::loop(); + graphics::focus::update(); } graphics::window::destroy(); diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index 00def97..ca7410e 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -9,6 +9,7 @@ using namespace sim::reactor; reactor::reactor(std::unique_ptr* rods, int w, int h, double cw, double ch) : cell_width(cw), cell_height(ch), width(w), height(h), size(w * h) { this->rods = std::vector>(w * h); + this->cursor = w * h; for(int i = 0; i < size; i++) { @@ -55,9 +56,14 @@ void reactor::update(double secs) } update_interactions(rods_lookup, secs / 2); + + if(rod_speed != 0) + { + update_selected(secs); + } } -void reactor::update_selected(int v) +void reactor::update_selected(double dt) { for(int i = 0; i < size; i++) { @@ -65,34 +71,34 @@ void reactor::update_selected(int v) if(r->is_selected()) { - r->update_rod_selected(v); + r->update_selected(rod_speed * dt); } } } int reactor::move_cursor(int d) { - for(int i = 0; i < size; i++) + goto logic; + + while(cursor == size || !rods[cursor]->should_display()) { - cursor = (cursor + d) % size; - +logic: cursor = (cursor + d) % (size + 1); + if(cursor < 0) { - cursor += size; + cursor += size + 1; } - if(rods[cursor]->should_select()) - { - return cursor; - } + if(d > 1) d = 1; + if(d < -1) d = -1; } - return 0; + return cursor; } void reactor::toggle_selected() { - if(rods[cursor]->should_select()) + if(cursor < size && rods[cursor]->should_select()) { rods[cursor]->toggle_selected(); } diff --git a/src/reactor/reactor.hpp b/src/reactor/reactor.hpp index 7ebc894..00c36a3 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -19,14 +19,14 @@ struct reactor const int size; std::vector> rods; - int cursor = 0; + double rod_speed = 0; + int cursor; reactor(std::unique_ptr* rods, int width, int height, double cell_width, double cell_height); reactor(const reactor& r); reactor(reactor&& r); void update(double secs); - void update_selected(int v); void get_stats(rod::val_t type, double& min, double& max); void get_rod_stats(int type, double& min, double& max); double get_total(rod::val_t type); @@ -37,6 +37,7 @@ private: void update_tile(double secs, int i, int x, int y); void update_interactions(int* rods_lookup, double secs); + void update_selected(double dt); }; } diff --git a/src/reactor/rod.cpp b/src/reactor/rod.cpp index d744e5d..29ae14d 100644 --- a/src/reactor/rod.cpp +++ b/src/reactor/rod.cpp @@ -43,12 +43,6 @@ void rod::interact(rod* o, double secs) } } -double rod::get_speed() const -{ - int m = motion < 0 ? -1 : 1; - return motion == 0 ? 0 : (std::pow(10, std::abs(motion)) * 1e-6 * m); -} - double rod::get_volume() const { auto r = (sim::reactor::reactor*)reactor; @@ -61,23 +55,5 @@ void rod::update_rod(double secs) double m = std::pow(0.5, secs / 879.4); vals[val_t::N_FAST] *= m; vals[val_t::N_SLOW] *= m; - - if(motion != 0 && !is_selected()) - { - motion = 0; - } - - if(motion != 0) - { - update_selected(get_speed() * secs); - } -} - -void rod::update_rod_selected(int m) -{ - motion += m; - - if(motion > 5) motion = 5; - if(motion < -5) motion = -5; } diff --git a/src/reactor/rod.hpp b/src/reactor/rod.hpp index 284da37..db714d7 100644 --- a/src/reactor/rod.hpp +++ b/src/reactor/rod.hpp @@ -35,7 +35,7 @@ public: virtual bool has_sensors(val_t t) const { return false; } virtual bool should_display() const { return false; } virtual bool should_select() const { return false; } - void update_rod_selected(int m); + virtual void update_selected(double a) { } double get_volume() const; constexpr void toggle_selected() { selected = !selected; } @@ -47,11 +47,6 @@ public: o << r.get_name() << "\n"; - if(r.is_selected()) - { - o << "Speed: " << r.get_speed() << "\n"; - } - r.display(o); o << "Heat: " << r.get(val_t::HEAT) << " C\n"; @@ -65,15 +60,12 @@ protected: double vals[VAL_N] = {0}; bool selected = false; - int motion = 0; virtual void display(std::ostream& o) const { }; virtual double get_k(val_t type) const { return 0; } virtual const char* get_name() const { return "Empty"; } - virtual void update_selected(double a) { } void update_rod(double secs); - double get_speed() const; }; } diff --git a/src/system.cpp b/src/system.cpp index 64b5797..e110c26 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -9,6 +9,8 @@ using namespace sim; +sim::system system::active; + system::system() { const char* layout[] = { diff --git a/src/system.hpp b/src/system.hpp index 148ea54..058a91f 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -14,6 +14,8 @@ namespace sim struct system { + static system active; + std::unique_ptr reactor; std::unique_ptr vessel; std::unique_ptr> valve; From 1336a68ec4c1628168298f4b18a0495e3d583cdc Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 31 Jan 2024 00:25:19 +1100 Subject: [PATCH 30/43] changed readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ce45d2..5a8595f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # How to build This is built using CMake. You will also need the required libraries to build. -This project currently only works on Linux. +This project currently is only tested on Linux. ``` mkdir build From c5496da87d9ab469c94304a738b12cd0fa1e0cfe Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 31 Jan 2024 00:30:26 +1100 Subject: [PATCH 31/43] fix GUI issues --- src/graphics/monitor/vessel.cpp | 4 +++- src/reactor/reactor.cpp | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/graphics/monitor/vessel.cpp b/src/graphics/monitor/vessel.cpp index d443e44..7b78fea 100644 --- a/src/graphics/monitor/vessel.cpp +++ b/src/graphics/monitor/vessel.cpp @@ -105,7 +105,9 @@ void vessel::update() ss << sys.reactor->get_total(sim::reactor::rod::val_t::N_FAST) << " mol\n\n\n"; ss << show( crod_min * 100 ) << " %\n"; ss << show( crod_max * 100 ) << " %\n"; - ss << show( sys.reactor->rod_speed * 100, 1e-5 ) << " %/s\n"; + ss << show( sys.reactor->rod_speed * 100, 1e6 ) << " %/s"; + if(sys.reactor->rod_speed == 0) ss << " (Stopped)"; + ss << "\n"; rmesh.load_text(ss.str().c_str(), 0.04); mesh2.bind(); diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index ca7410e..62c69bc 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -57,6 +57,11 @@ void reactor::update(double secs) update_interactions(rods_lookup, secs / 2); + if(std::abs(rod_speed) < 1e-6) + { + rod_speed = 0; + } + if(rod_speed != 0) { update_selected(secs); From 8ed3bd379a175046bab4bb7c4df05e169f4e4f63 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 31 Jan 2024 00:38:27 +1100 Subject: [PATCH 32/43] use pico-moles instead of moles for displaying neutrons --- src/graphics/monitor/vessel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphics/monitor/vessel.cpp b/src/graphics/monitor/vessel.cpp index 7b78fea..fe528a9 100644 --- a/src/graphics/monitor/vessel.cpp +++ b/src/graphics/monitor/vessel.cpp @@ -101,8 +101,8 @@ void vessel::update() ss << show( sys.vessel->get_void_ratio() * 100 ) << " %\n\n\n\n\n\n\n"; ss << show( temp_min ) << " C\n"; ss << show( temp_max ) << " C\n\n\n"; - ss << sys.reactor->get_total(sim::reactor::rod::val_t::N_SLOW) << " mol\n"; - ss << sys.reactor->get_total(sim::reactor::rod::val_t::N_FAST) << " mol\n\n\n"; + ss << ( sys.reactor->get_total(sim::reactor::rod::val_t::N_SLOW) * 1e12 ) << " pmol\n"; + ss << ( sys.reactor->get_total(sim::reactor::rod::val_t::N_FAST) * 1e12 ) << " pmol\n\n\n"; ss << show( crod_min * 100 ) << " %\n"; ss << show( crod_max * 100 ) << " %\n"; ss << show( sys.reactor->rod_speed * 100, 1e6 ) << " %/s"; From c25f8efbfe32ef8e5a8fae979686a36c37e85391 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Wed, 31 Jan 2024 00:53:28 +1100 Subject: [PATCH 33/43] fix control rod selector --- src/reactor/reactor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index 62c69bc..11799f6 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -85,7 +85,7 @@ int reactor::move_cursor(int d) { goto logic; - while(cursor == size || !rods[cursor]->should_display()) + while(cursor != size && !rods[cursor]->should_display()) { logic: cursor = (cursor + d) % (size + 1); From e734eefdb72ed53a5072717cc53913739f28c53c Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Fri, 2 Feb 2024 13:05:28 +1100 Subject: [PATCH 34/43] fixed simulation speed consistency issues, added SCRAM, added text to buttons --- assets/model/reactor_core_scram.stl | Bin 0 -> 6284 bytes assets/scene-baked.glb | 4 +- assets/shader/main.vsh | 5 ++- assets/unbaked/scene.blend | 4 +- assets/unbaked/scene/Keys.001.png | 3 ++ assets/unbaked/scene/SCRAM.png | 3 ++ src/graphics/input/keyboard.cpp | 20 +++++++++ src/graphics/mesh/glmesh.cpp | 3 +- src/graphics/mesh/mesh.cpp | 5 +++ src/graphics/mesh/mesh.hpp | 1 + src/graphics/monitor/core.cpp | 18 +++++--- src/graphics/monitor/core.hpp | 2 +- src/graphics/monitor/vessel.cpp | 15 ++++--- src/graphics/shader.cpp | 2 + src/graphics/shader.hpp | 1 + src/graphics/ui.cpp | 67 ++++++++++++++++++++++++++++ src/graphics/ui.hpp | 12 +++++ src/graphics/widget/clock.cpp | 51 +++++++++++++++++++++ src/graphics/widget/clock.hpp | 19 ++++++++ src/graphics/window.cpp | 32 +++++++------ src/graphics/window.hpp | 3 +- src/main.cpp | 4 +- src/reactor/builder.cpp | 4 +- src/reactor/builder.hpp | 2 +- src/reactor/control/boron_rod.cpp | 13 +++--- src/reactor/control/boron_rod.hpp | 8 ++-- src/reactor/coolant/pipe.cpp | 8 ++-- src/reactor/fuel/fuel_rod.cpp | 12 ++++- src/reactor/fuel/fuel_rod.hpp | 1 + src/reactor/fuel/half_life.hpp | 6 ++- src/reactor/fuel/sample.cpp | 10 ++--- src/reactor/fuel/sample.hpp | 3 ++ src/reactor/reactor.cpp | 67 +++++++++++++++++++++++++++- src/reactor/reactor.hpp | 6 +++ src/reactor/rod.cpp | 31 +++++++++++-- src/reactor/rod.hpp | 12 +++-- src/system.cpp | 7 +-- src/system.hpp | 1 + 38 files changed, 386 insertions(+), 79 deletions(-) create mode 100644 assets/model/reactor_core_scram.stl create mode 100644 assets/unbaked/scene/Keys.001.png create mode 100644 assets/unbaked/scene/SCRAM.png create mode 100644 src/graphics/ui.cpp create mode 100644 src/graphics/ui.hpp create mode 100644 src/graphics/widget/clock.cpp create mode 100644 src/graphics/widget/clock.hpp diff --git a/assets/model/reactor_core_scram.stl b/assets/model/reactor_core_scram.stl new file mode 100644 index 0000000000000000000000000000000000000000..ddfcefd888539ce75a94d9518fc1633ec7c47da2 GIT binary patch literal 6284 zcmbVPO=uof6n#5QY*o4_G_jJ#7C}r=QDgJ>4MNDyg%Bf6BE}|7z-nSBq0$mRbRiTM z5ke(h*g`=eRd@a@%r{t)khQMFWreJ&WRZe(5$~BhXXZR69|XHP&ADgJd+*+N@B0QX z&CSkVoSN)7KR^3k$Ez2n-k+SB?|Sj{bEjVj|L5No6P;;n^}oBmSaW?x^2YE~a((5P z{onC-LnmtEttXPU-l+sQgbP1!^?$IBLZgWNOMid4Jka;xV&GAC5MD5O-RfR?oyT>z@y|Xu#GY@5m;c=|@R5IQ=k?-SqQ*`g6 z&?sW}7_%z;_m{1H>_Zu1cnlApNOf5W@p~{`M+8{#nbWK}8WqxxzhceyrL2$P|onru^=$-6v%qHb2&EepD11 zMeJX)`O&cXktviR#y&pYJudmM`O&cXQBi0Vv3pns8#X^Og)+qOSZFyeb=6FvQN-?H zm6+Q6$UKxGhR0IR6LLSADKv`MJ%;T2L-~zTh8P~}OYL&cle>fIr}?dA6uzZV#O`68 zA!TP!h8P|zN7{={6oPf1l--9>_?AWyyN7kSls!=yVt8z?w-p^Vgk}njB6bg(2C2=D ztWzsP43FEZZAmmgQkx$Yg+>ua9{uQv$`ErO6@)v+I#PC37%Xi6$RmmUuD}zz^mPQe~hrMJ{zGRdk zh6gINd|%mXDdlU4QTUcd5xYlzqiV>9GQ{w}eJtOJ%@i6%>>hb%$UKxGh6nme`QB}& z&?sW}*gSmnN9-Yldh~WiZ!h2KFszelTfh%kuuW(h$K*9GFzQlJ>yeeg|D!zBH+VWjeYU)u(#?JY&z=@PE06kr0 zbVhML7W*hNsEkf$eGQarV$SKV?-G3n#R_HgMW!#n5EdRi`jhMG*>3=zqP|7-?HhY2 z0|omPJlI!ag)&$b`v+EA-t$sZkA7ic=e(&ntMW#vB4)}vp*3B`<8+nLI@Sse;n3x_ zB7@3kRcgIKas3B(sz;eE9PKTqWO<=Zu=-YgEE>;%~Q6Im$?sRG;8sx zmph63m7cVoVF=&++EsWcqbGW>v(od?{e{r^TUU_}Wpqym+lQ|3V5?HU*u#DsH0-xQ z)`59G==#LyAKJA;u-^s^`)yEBXcV!}5BqJfX1@(Gg)+q6qsqGyg8eqYOTVJfSwid{ zlJk_`b;iF7%z5R1AA0y2Z&a!b&j(Sw^R7@2Wmt)bVqd8$lwr;h#okp_D8u_f1*Xsb z3Np+&qS%97v3d9!?_jD7Z!MzO=Ut&^r3|YbQJf{N(6fq}@)sTQgx{QnN3kkpSmB7` zOmv0qqsX8#>~M(U+;xSnN*UgBL~*uP70R&hQi18Szk&>JEu#2FsVbD=G(Z&JL9VcT z6lbLjZ!MzYTcA5vMk`-yx{S9Ol`5lktQDHgsA5&h0JAE!-t5a)+OnRwxA#$0iCSZE z^_1(XXSbI1qZOmIP{v~)%4q&Iv(cufpHBTXY@nIY1N2-kGMo&Fnl`?wLdWM)$ t-+u49da8_`=)ulPPeJz=uSyx+ldd*|?Y+ld;;NL<6&`F=>Ze}{;Xh4Ry{G^H literal 0 HcmV?d00001 diff --git a/assets/scene-baked.glb b/assets/scene-baked.glb index 1c6cb0b..2d48ad4 100644 --- a/assets/scene-baked.glb +++ b/assets/scene-baked.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e95d9c35819ae80c6a1bffd98b5127e905b18a3dca9bf7ce689d3caab487ca4c -size 66429308 +oid sha256:e8e38fa332dfb5a4b589915a2b08cad209d2b25668a86881c1fbd50e90c389bf +size 69580444 diff --git a/assets/shader/main.vsh b/assets/shader/main.vsh index fbe8a82..d7d62c3 100644 --- a/assets/shader/main.vsh +++ b/assets/shader/main.vsh @@ -8,6 +8,7 @@ layout (location = 2) in vec4 aPos; layout (location = 3) in vec3 aNormal; uniform mat4 model; +uniform mat4 camera; uniform mat4 projection; out float brightness; @@ -16,8 +17,8 @@ out vec2 texPos; void main() { - vec4 pos = model * aPos; - vec3 cNormal = vec3(0.f, 0.f, 1.f) * mat3(model); + vec4 pos = camera * model * aPos; + vec3 cNormal = vec3(0.f, 0.f, 1.f) * mat3(camera * model); brightness = dot(normalize(aNormal), normalize(cNormal)) * 0.25f + 0.75f; diff --git a/assets/unbaked/scene.blend b/assets/unbaked/scene.blend index 9e7ba4a..b894166 100644 --- a/assets/unbaked/scene.blend +++ b/assets/unbaked/scene.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe3f5df6086f9ddfe253116efadb3dbcbbca826b5cfcb3a320dc63c7e85b6ff6 -size 79959028 +oid sha256:de45f07d6afa36e19985970817efc827b060d18a7166350dfd27d2df8d1b120f +size 80493828 diff --git a/assets/unbaked/scene/Keys.001.png b/assets/unbaked/scene/Keys.001.png new file mode 100644 index 0000000..8885f2b --- /dev/null +++ b/assets/unbaked/scene/Keys.001.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7923df8fc1e4f5b838a8afe3abce139a6fba904f3753bde247a66d1c828cdf1 +size 13935 diff --git a/assets/unbaked/scene/SCRAM.png b/assets/unbaked/scene/SCRAM.png new file mode 100644 index 0000000..9f54c80 --- /dev/null +++ b/assets/unbaked/scene/SCRAM.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19819dc562b088ae7815a897e133238864639f6df2c32745fd12db17ef16accd +size 13607 diff --git a/src/graphics/input/keyboard.cpp b/src/graphics/input/keyboard.cpp index 285a2a6..9145ba6 100644 --- a/src/graphics/input/keyboard.cpp +++ b/src/graphics/input/keyboard.cpp @@ -9,6 +9,7 @@ #include "../window.hpp" #include "../resize.hpp" #include "../camera.hpp" +#include "../../system.hpp" using namespace sim::graphics; @@ -24,6 +25,25 @@ static void cb_keypress(GLFWwindow* win, int key, int sc, int action, int mods) if(action == GLFW_PRESS) { pressed[key] = true; + + switch(key) + { + case GLFW_KEY_1: + sim::system::active.speed = 1; + break; + case GLFW_KEY_2: + sim::system::active.speed = 10; + break; + case GLFW_KEY_3: + sim::system::active.speed = 60; + break; + case GLFW_KEY_4: + sim::system::active.speed = 600; + break; + case GLFW_KEY_5: + sim::system::active.speed = 3600; + break; + } } if(action == GLFW_RELEASE) diff --git a/src/graphics/mesh/glmesh.cpp b/src/graphics/mesh/glmesh.cpp index ea8e82a..fe8bca1 100644 --- a/src/graphics/mesh/glmesh.cpp +++ b/src/graphics/mesh/glmesh.cpp @@ -59,8 +59,7 @@ void glmesh::bind() void glmesh::uniform() { - glm::mat4 m = camera::get_matrix() * model_matrix; - glUniformMatrix4fv(shader::gl_model, 1, false, &m[0][0]); + glUniformMatrix4fv(shader::gl_model, 1, false, &model_matrix[0][0]); glUniformMatrix4fv(shader::gl_tex_mat, 1, false, &colour_matrix[0][0]); } diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp index 725046f..db7fde6 100644 --- a/src/graphics/mesh/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -101,6 +101,11 @@ bool mesh::check_focus(double len) const return focus::is_triggered() && check_intersect(camera::get_pos(), camera::get_normal() * len); } +bool mesh::check_focus() const +{ + return check_focus(2.5); +} + bool mesh::check_intersect(vec3 pos, vec3 path) const { double l = glm::length(path); diff --git a/src/graphics/mesh/mesh.hpp b/src/graphics/mesh/mesh.hpp index 9aea398..0f6df3f 100644 --- a/src/graphics/mesh/mesh.hpp +++ b/src/graphics/mesh/mesh.hpp @@ -28,6 +28,7 @@ struct mesh void add(const mesh& o, glm::mat4 mat); mesh to_lines() const; + bool check_focus() const; bool check_focus(double len) const; bool check_intersect(glm::vec<3, double> pos, glm::vec<3, double> path) const; glm::vec<3, double> calc_intersect(glm::vec<3, double> pos, glm::vec<3, double> path) const; diff --git a/src/graphics/monitor/core.cpp b/src/graphics/monitor/core.cpp index f119f83..6b2a0fe 100644 --- a/src/graphics/monitor/core.cpp +++ b/src/graphics/monitor/core.cpp @@ -19,7 +19,7 @@ struct core_focus_t : public focus::focus_t { virtual void on_cursor_pos(double x, double y) { - sim::system::active.reactor->rod_speed -= y * 1e-6; + sim::system::active.reactor->add_rod_speed(-y * 1e-6); } void set_all(bool state) @@ -28,9 +28,9 @@ struct core_focus_t : public focus::focus_t { sim::reactor::rod* r = sim::system::active.reactor->rods[i].get(); - if(r->should_select() && (r->is_selected() != state)) + if(r->should_select()) { - r->toggle_selected(); + r->selected = state; } } } @@ -77,7 +77,7 @@ struct core_focus_t : public focus::focus_t toggle_auto(); break; case GLFW_KEY_X: - sim::system::active.reactor->rod_speed = 0; + sim::system::active.reactor->reset_rod_speed(); break; case GLFW_KEY_Q: set_all(true); @@ -112,14 +112,20 @@ void core::init() mesh2.set(rmesh2, GL_STATIC_DRAW); mesh_click.load_model("../assets/model/", "reactor_core_input.stl"); + mesh_scram.load_model("../assets/model/", "reactor_core_scram.stl"); } void core::update() { - if(mesh_click.check_focus(2)) + if(mesh_click.check_focus()) { focus::set(std::make_unique()); } + + if(mesh_scram.check_focus()) + { + sim::system::active.reactor->scram(); + } } void core::render() @@ -169,7 +175,7 @@ void core::render() mesh2.render(); } - if(r->is_selected()) + if(r->selected) { mesh2.model_matrix = mat * mat_select; mesh2.colour_matrix = arrays::colour({1, 1, 0, 1}); diff --git a/src/graphics/monitor/core.hpp b/src/graphics/monitor/core.hpp index ccd71a3..677bc31 100644 --- a/src/graphics/monitor/core.hpp +++ b/src/graphics/monitor/core.hpp @@ -9,7 +9,7 @@ namespace sim::graphics::monitor class core { - sim::graphics::mesh mesh_click; + sim::graphics::mesh mesh_click, mesh_scram; sim::graphics::glmesh mesh1, mesh2; public: diff --git a/src/graphics/monitor/vessel.cpp b/src/graphics/monitor/vessel.cpp index fe528a9..6f133c7 100644 --- a/src/graphics/monitor/vessel.cpp +++ b/src/graphics/monitor/vessel.cpp @@ -37,10 +37,11 @@ void vessel::init() ss << "Steam\n"; ss << "Pressure\n"; ss << "Level\n"; - ss << "Void Ratio\n\n\n\n"; + ss << "Void Ratio\n\n"; ss << "Reactor Core\n\n"; + ss << "Energy Output\n"; + ss << "Neutron Flux\n\n"; ss << "Temperature\nMin\nMax\n\n"; - ss << "Neutron Flux\nSlow\nFast\n\n"; ss << "Control Rods\nMin\nMax\nSpeed\n"; rmesh.load_text(ss.str().c_str(), 0.04); @@ -98,13 +99,13 @@ void vessel::update() ss << show( sys.vessel->get_steam() ) << " g\n"; ss << show( sys.vessel->get_pressure() * 0.001 ) << " kPa\n"; ss << show( sys.vessel->get_level() ) << " / " << show( sys.vessel->get_volume() ) << " L\n"; - ss << show( sys.vessel->get_void_ratio() * 100 ) << " %\n\n\n\n\n\n\n"; + ss << show( sys.vessel->get_void_ratio() * 100 ) << " %\n\n\n\n"; + ss << show( sys.reactor->get_energy_output() * 0.001 ) << " kW\n"; + ss << show( sys.reactor->get_flux() ) << " n/cm2/s\n\n\n"; ss << show( temp_min ) << " C\n"; ss << show( temp_max ) << " C\n\n\n"; - ss << ( sys.reactor->get_total(sim::reactor::rod::val_t::N_SLOW) * 1e12 ) << " pmol\n"; - ss << ( sys.reactor->get_total(sim::reactor::rod::val_t::N_FAST) * 1e12 ) << " pmol\n\n\n"; - ss << show( crod_min * 100 ) << " %\n"; - ss << show( crod_max * 100 ) << " %\n"; + ss << show( 100 - crod_max * 100 ) << " %\n"; + ss << show( 100 - crod_min * 100 ) << " %\n"; ss << show( sys.reactor->rod_speed * 100, 1e6 ) << " %/s"; if(sys.reactor->rod_speed == 0) ss << " (Stopped)"; ss << "\n"; diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp index e02cdd4..974d941 100644 --- a/src/graphics/shader.cpp +++ b/src/graphics/shader.cpp @@ -15,6 +15,7 @@ static unsigned int prog_id; int shader::gl_tex_mat; int shader::gl_model; +int shader::gl_camera; int shader::gl_projection; static int load_shader(const char* src, int type) @@ -68,6 +69,7 @@ unsigned int shader::init_program() gl_tex_mat = glGetUniformLocation(prog_id, "tex_mat"); gl_model = glGetUniformLocation(prog_id, "model"); + gl_camera = glGetUniformLocation(prog_id, "camera"); gl_projection = glGetUniformLocation(prog_id, "projection"); glUseProgram(prog_id); diff --git a/src/graphics/shader.hpp b/src/graphics/shader.hpp index e144683..b6258ea 100644 --- a/src/graphics/shader.hpp +++ b/src/graphics/shader.hpp @@ -6,6 +6,7 @@ namespace sim::graphics::shader extern int gl_tex_mat; extern int gl_model; +extern int gl_camera; extern int gl_projection; unsigned int init_program(); diff --git a/src/graphics/ui.cpp b/src/graphics/ui.cpp new file mode 100644 index 0000000..3aaaabb --- /dev/null +++ b/src/graphics/ui.cpp @@ -0,0 +1,67 @@ + +#include "ui.hpp" + +#include +#include + +#include + +#include "mesh/mesh.hpp" +#include "mesh/glmesh.hpp" +#include "mesh/arrays.hpp" +#include "mesh/font.hpp" +#include "mesh/texture.hpp" +#include "resize.hpp" +#include "shader.hpp" + +#include "widget/clock.hpp" + +using namespace sim::graphics; + +static glmesh StaticMeshData; +static widget::clock WidgetClock; + +void ui::init() +{ + mesh m; + + unsigned int handle = texture::handle_white; + const unsigned int indices[] = {0, 1, 3, 0, 3, 2}; + const arrays::vertex vertices[] = { + arrays::vertex(handle, {0, 0}, {-1, -1, 0, 1}, {0, 0, -1}), + arrays::vertex(handle, {0, 1}, {-1, 1, 0, 1}, {0, 0, -1}), + arrays::vertex(handle, {1, 0}, { 1, -1, 0, 1}, {0, 0, -1}), + arrays::vertex(handle, {1, 1}, { 1, 1, 0, 1}, {0, 0, -1}), + }; + + m.set_indices(indices, 6); + m.set_vertices(vertices, 4); + + StaticMeshData.bind(); + StaticMeshData.set(m, GL_STATIC_DRAW); + StaticMeshData.colour_matrix = glm::scale(glm::mat4(1), glm::vec3(1) * 0.75f); +} + +void ui::update(double dt) +{ + WidgetClock.update(dt); +} + +void ui::render() +{ + glClear(GL_DEPTH_BUFFER_BIT); + + glm::vec2 wsize(resize::get_size() / 2); + + glm::mat4 mat_projection = glm::mat4(1); + glm::mat4 mat_camera = glm::scale(glm::mat4(1), glm::vec3(1.0f / wsize * glm::vec2(1, -1), -1)); + glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); + glUniformMatrix4fv(shader::gl_camera, 1, false, &mat_camera[0][0]); + + StaticMeshData.bind(); + StaticMeshData.uniform(); + StaticMeshData.render(); + + WidgetClock.render(); +} + diff --git a/src/graphics/ui.hpp b/src/graphics/ui.hpp new file mode 100644 index 0000000..e606602 --- /dev/null +++ b/src/graphics/ui.hpp @@ -0,0 +1,12 @@ + +#pragma once + +namespace sim::graphics::ui +{ + +void init(); +void update(double dt); +void render(); + +}; + diff --git a/src/graphics/widget/clock.cpp b/src/graphics/widget/clock.cpp new file mode 100644 index 0000000..4ba09ae --- /dev/null +++ b/src/graphics/widget/clock.cpp @@ -0,0 +1,51 @@ + +#include "clock.hpp" + +#include +#include + +#include + +#include +#include +#include + +#include "../mesh/arrays.hpp" +#include "../mesh/font.hpp" +#include "../mesh/arrays.hpp" +#include "../resize.hpp" +#include "../../system.hpp" + +using namespace sim::graphics::widget; + +void clock::update(double dt) +{ + mesh m; + glm::vec2 wsize(resize::get_size() / 2); + std::stringstream ss; + + int t_s = std::fmod(at, 60); + int t_m = std::fmod(at / 60, 60); + int t_h = std::fmod(at / 3600, 24); + + ss << "Time: " << std::setfill('0') << std::setw(2) << t_h << ":"; + ss << std::setfill('0') << std::setw(2) << t_m << ":"; + ss << std::setfill('0') << std::setw(2) << t_s << "\n"; + ss << "Day: " << std::floor(at / (3600 * 24)) << "\n"; + at += dt * sim::system::active.speed; + + m.load_text(ss.str().c_str(), 20); + + data.bind(); + data.model_matrix = glm::translate(glm::mat4(1), glm::vec3(-wsize + glm::vec2(2, 2), 0)); + data.colour_matrix = arrays::colour({1, 1, 1, 1}); + data.set(m, GL_DYNAMIC_DRAW); +} + +void clock::render() +{ + data.bind(); + data.uniform(); + data.render(); +} + diff --git a/src/graphics/widget/clock.hpp b/src/graphics/widget/clock.hpp new file mode 100644 index 0000000..5228949 --- /dev/null +++ b/src/graphics/widget/clock.hpp @@ -0,0 +1,19 @@ + +#pragma once + +#include "../mesh/glmesh.hpp" + +namespace sim::graphics::widget +{ + +struct clock +{ + double at = 3600 * 12; + glmesh data; + + void update(double dt); + void render(); +}; + +}; + diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 9a5c4b5..8c9843d 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -21,13 +21,14 @@ #include "monitor/vessel.hpp" #include "monitor/core.hpp" #include "mesh/texture.hpp" +#include "ui.hpp" using namespace sim::graphics; static GLFWwindow* win; static bool win_should_close = false; -static glmesh MeshScene, MeshDebug; +static glmesh MeshScene; static monitor::vessel MonitorVessel; static monitor::core MonitorCore; @@ -81,8 +82,8 @@ void window::create() glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -93,18 +94,16 @@ void window::create() resize::init(); texture::init(); font::init(); + ui::init(); shader::init_program(); sim::system& sys = sim::system::active; + mesh m; - sys.scene.load_model("../assets", "scene-baked.glb"); + m.load_model("../assets", "scene-baked.glb"); MeshScene.bind(); - MeshScene.set(sys.scene, GL_STATIC_DRAW); - -// sys.scene.load_model("../assets/model", "reactor_core_input.stl"); -// MeshDebug.bind(); -// MeshDebug.set(sys.scene, GL_STATIC_DRAW); + MeshScene.set(m, GL_STATIC_DRAW); sys.scene.load_model("../assets/model", "scene_collisions.stl"); // MeshCollisionScene.bind(); @@ -117,22 +116,25 @@ void window::create() glViewport(0, 0, 800, 600); } -void window::loop() +void window::update(double dt) { glfwPollEvents(); MonitorCore.update(); MonitorVessel.update(); - glm::mat4 mat_projection = glm::perspective(glm::radians(80.0f), resize::get_aspect(), 0.01f, 20.f); + ui::update(dt); +} + +void window::render() +{ + glm::mat4 mat_camera = camera::get_matrix(); + glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), resize::get_aspect(), 0.01f, 20.f); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); + glUniformMatrix4fv(shader::gl_camera, 1, false, &mat_camera[0][0]); glClearColor(0, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - -// MeshDebug.bind(); -// MeshDebug.uniform(); -// MeshDebug.render(); MeshScene.bind(); MeshScene.uniform(); @@ -141,6 +143,8 @@ void window::loop() MonitorCore.render(); MonitorVessel.render(); + ui::render(); + glfwSwapBuffers(win); } diff --git a/src/graphics/window.hpp b/src/graphics/window.hpp index 58c934d..8626cce 100644 --- a/src/graphics/window.hpp +++ b/src/graphics/window.hpp @@ -10,7 +10,8 @@ namespace sim::graphics::window bool should_close(); void create(); -void loop(); +void update(double dt); +void render(); void destroy(); void close(); diff --git a/src/main.cpp b/src/main.cpp index 7665d81..d07598c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,9 +41,11 @@ int main() clock += passed; sim::system::active.update(dt); + graphics::camera::update(dt); - graphics::window::loop(); + graphics::window::update(dt); graphics::focus::update(); + graphics::window::render(); } graphics::window::destroy(); diff --git a/src/reactor/builder.cpp b/src/reactor/builder.cpp index 2c14fee..f78509f 100644 --- a/src/reactor/builder.cpp +++ b/src/reactor/builder.cpp @@ -7,7 +7,7 @@ using namespace sim::reactor; -sim::reactor::reactor sim::reactor::builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, control::boron_rod br, coolant::vessel& v, const char** lines) +sim::reactor::reactor sim::reactor::builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, coolant::vessel& v, const char** lines) { std::vector> arr(W * H); @@ -23,7 +23,7 @@ sim::reactor::reactor sim::reactor::builder(const int W, const int H, const doub r = std::make_unique(fr); break; case 'C': - r = std::make_unique(br); + r = std::make_unique(v); break; case 'G': r = std::make_unique(); diff --git a/src/reactor/builder.hpp b/src/reactor/builder.hpp index 8bfa264..ecb1a5f 100644 --- a/src/reactor/builder.hpp +++ b/src/reactor/builder.hpp @@ -13,7 +13,7 @@ namespace sim::reactor { -reactor builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, control::boron_rod br, coolant::vessel& v, const char** lines); +reactor builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, coolant::vessel& v, const char** lines); }; diff --git a/src/reactor/control/boron_rod.cpp b/src/reactor/control/boron_rod.cpp index 7a9e6e8..12d893d 100644 --- a/src/reactor/control/boron_rod.cpp +++ b/src/reactor/control/boron_rod.cpp @@ -9,9 +9,9 @@ constexpr double boron_density = 2340000; // g/m^3 constexpr double boron_molar_mass = 10; // g/mol constexpr double boron_molar_density = boron_density / boron_molar_mass; // mol/m^3 -boron_rod::boron_rod(coolant::vessel& v, double max) : coolant::pipe(v), max(max) +boron_rod::boron_rod(coolant::vessel& v) : coolant::pipe(v) { - + } void boron_rod::display(std::ostream& o) const @@ -39,15 +39,12 @@ void boron_rod::update(double secs) update_rod(secs); - double k = (1 - absorbed) * inserted * max; - double m = 1 - std::pow(0.5, secs * -std::log2(1 - k)); + double m = (1 - absorbed) * inserted; double r_fast = vals[val_t::N_FAST] * m; - double r_slow = vals[val_t::N_SLOW] * m; vals[val_t::N_FAST] -= r_fast; - vals[val_t::N_SLOW] -= r_slow; - absorbed += (r_fast + r_slow) / limit; - + absorbed += r_fast / limit; + update_pipe(secs); } diff --git a/src/reactor/control/boron_rod.hpp b/src/reactor/control/boron_rod.hpp index f7347c2..63ef41a 100644 --- a/src/reactor/control/boron_rod.hpp +++ b/src/reactor/control/boron_rod.hpp @@ -6,10 +6,8 @@ namespace sim::reactor::control { -class boron_rod : public sim::reactor::coolant::pipe +class boron_rod : public coolant::pipe { - const double max; - double inserted = 1; double absorbed = 0; @@ -19,8 +17,8 @@ class boron_rod : public sim::reactor::coolant::pipe virtual int get_id() const { return 5; } public: - - boron_rod(coolant::vessel& v, double max); + + boron_rod(coolant::vessel& v); virtual void update(double secs); void set_reactivity(double a); diff --git a/src/reactor/coolant/pipe.cpp b/src/reactor/coolant/pipe.cpp index 7bcd4e0..984d229 100644 --- a/src/reactor/coolant/pipe.cpp +++ b/src/reactor/coolant/pipe.cpp @@ -12,7 +12,7 @@ pipe::pipe(coolant::vessel& v) double pipe::get_k(val_t type) const { - return vessel->get_level() / vessel->get_volume() * (1 - vessel->get_void_ratio()) * 0.5; + return vessel->get_level() / vessel->get_volume() * 0.5; } void pipe::update(double secs) @@ -24,10 +24,10 @@ void pipe::update(double secs) void pipe::update_pipe(double secs) { sim::reactor::reactor* r = (sim::reactor::reactor*)reactor; - double m = r->cell_width * r->cell_width * r->cell_height * 1e6; + double m_heat = r->cell_width * r->cell_width * r->cell_height * 1e6; - vals[val_t::HEAT] = vessel->add_heat(m, vals[val_t::HEAT]); - vals[val_t::N_SLOW] += vals[val_t::N_FAST]; + vals[val_t::HEAT] = vessel->add_heat(m_heat, vals[val_t::HEAT]); + vals[val_t::N_SLOW] += vals[val_t::N_FAST] * (1 - vessel->get_void_ratio()); vals[val_t::N_FAST] = 0; } diff --git a/src/reactor/fuel/fuel_rod.cpp b/src/reactor/fuel/fuel_rod.cpp index 954a8dd..fe2c250 100644 --- a/src/reactor/fuel/fuel_rod.cpp +++ b/src/reactor/fuel/fuel_rod.cpp @@ -21,11 +21,18 @@ void fuel_rod::display(std::ostream& o) const o << "Fuel: " << (s.get_fuel() * mol) << " / " << (s.get_mass() * mol) << " mol\n"; o << "Efficiency: " << (s.get_efficiency() * 100) << " %\n"; - o << "Output: " << (s.get_energy() * mol * energy_density) << " W\n"; + o << "Output: " << get_energy_output() << " W\n"; o << "Iodine: " << (s.get_i_135() * mol) << " mol\n"; o << "Xenon: " << (s.get_xe_135() * mol) << " mol\n"; } +double fuel_rod::get_energy_output() const +{ + double mol = fuel_molar_density * get_volume(); + + return s.get_energy() * mol * energy_density; +} + static float map(float v, float imin, float imax, float omin, float omax) { return (v - imin) * (omax - omin) / (imax - imin) + omin; @@ -77,11 +84,12 @@ void fuel_rod::update(double secs) s.clear_energy(); s.clear_fast_neutrons(); + s.clear_slow_neutrons(); s.add_slow_neutrons(vals[val_t::N_SLOW] / mol); s.update(secs); vals[val_t::HEAT] += s.get_energy() * mol * energy_density * secs; vals[val_t::N_FAST] += s.get_fast_neutrons() * mol; - vals[val_t::N_SLOW] = 0; + vals[val_t::N_SLOW] = s.get_slow_neutrons() * mol; } diff --git a/src/reactor/fuel/fuel_rod.hpp b/src/reactor/fuel/fuel_rod.hpp index e2eebb1..ff4be62 100644 --- a/src/reactor/fuel/fuel_rod.hpp +++ b/src/reactor/fuel/fuel_rod.hpp @@ -17,6 +17,7 @@ class fuel_rod : public sim::reactor::rod virtual bool has_sensors(val_t t) const { return true; } virtual const char* get_name() const { return "Fuel"; } virtual int get_id() const { return 1; } + virtual double get_energy_output() const; virtual glm::vec4 get_colour() const; public: diff --git a/src/reactor/fuel/half_life.hpp b/src/reactor/fuel/half_life.hpp index 1e79e83..3b833c5 100644 --- a/src/reactor/fuel/half_life.hpp +++ b/src/reactor/fuel/half_life.hpp @@ -10,11 +10,15 @@ const double Te_135 = 19; const double I_135 = 23652; const double Xe_135 = 32904; const double Cs_135 = 41971608000000; +const double U_235 = 2.22e16; +const double U_238 = 1.41e17; -constexpr double get(double secs, double hl) noexcept +template +constexpr T get(T secs, T hl) noexcept { return std::pow(0.5, secs / hl); } + }; diff --git a/src/reactor/fuel/sample.cpp b/src/reactor/fuel/sample.cpp index 8021191..8638ba2 100644 --- a/src/reactor/fuel/sample.cpp +++ b/src/reactor/fuel/sample.cpp @@ -6,26 +6,26 @@ using namespace sim::reactor::fuel; -static const double NEUTRON_BG = 1e-25; +constexpr double NEUTRON_BG = 1e-30; sample::sample(double fuel) { this->fuel = fuel; + this->u_238 = 1 - fuel; this->mass = 1; } void sample::update(double secs) { double m; - + // decay waste and extract products waste.update(secs); fast_neutrons += waste.extract_neutrons(); - energy += waste.extract_energy() * (1.0 / 30.0); + energy += waste.extract_energy() * (1.0 / 30.0) / secs; // decay Xe-135 - m = half_life::get(secs, half_life::Xe_135); - xe_135 *= m; + xe_135 *= half_life::get(secs, half_life::Xe_135); // decay I-135 into Xe-135 m = half_life::get(secs, half_life::I_135); diff --git a/src/reactor/fuel/sample.hpp b/src/reactor/fuel/sample.hpp index bad2c8c..777479c 100644 --- a/src/reactor/fuel/sample.hpp +++ b/src/reactor/fuel/sample.hpp @@ -19,6 +19,7 @@ class sample double i_135 = 0; double xe_135 = 0; double te_135 = 0; + double u_238 = 0; double mass = 0; double energy = 0; // W @@ -39,6 +40,7 @@ public: constexpr double get_mass() const { return mass; } constexpr double get_energy() const { return energy; } constexpr double get_fast_neutrons() const { return fast_neutrons; } + constexpr double get_slow_neutrons() const { return slow_neutrons; } constexpr double get_volume() const { return mass + xe_135 * Xe_135_M; } constexpr double get_efficiency() const { return efficiency; } constexpr double get_te_135() const { return te_135; } @@ -47,6 +49,7 @@ public: constexpr void clear_energy() { energy = 0; } constexpr void clear_fast_neutrons() { fast_neutrons = 0; } + constexpr void clear_slow_neutrons() { slow_neutrons = 0; } constexpr void add_slow_neutrons(double a) { slow_neutrons += a; } friend std::ostream& operator<<(std::ostream& o, const sample& s) diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index 11799f6..809dd02 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -41,9 +41,45 @@ reactor::reactor(const reactor& o) : cell_width(o.cell_width), cell_height(o.cel } } +void reactor::reset_rod_speed() +{ + rod_speed = 0; +} + +void reactor::add_rod_speed(double a) +{ + rod_speed -= a; + + if(rod_speed < -0.2) + rod_speed = -0.2; + if(rod_speed > 0.2) + rod_speed = 0.2; +} + +void reactor::scram() +{ + rod_speed = 0.2; + + for(int i = 0; i < size; i++) + { + if(rods[i]->should_select()) + { + rods[i]->selected = true; + } + } +} + void reactor::update(double secs) { int rods_lookup[size]; + double temp_min, temp_max; + + get_stats(rod::val_t::HEAT, temp_min, temp_max); + + if(temp_max > 360) + { + scram(); + } for(int i = 0; i < size; i++) { @@ -74,7 +110,7 @@ void reactor::update_selected(double dt) { rod* r = rods[i].get(); - if(r->is_selected()) + if(r->selected) { r->update_selected(rod_speed * dt); } @@ -155,6 +191,35 @@ double reactor::get_total(rod::val_t type) return v; } +double reactor::get_average(rod::val_t type) +{ + return get_total(type) / size; +} + +double reactor::get_flux() +{ + double v = 0; + + for(int i = 0; i < size; i++) + { + v += rods[i]->get_flux(); + } + + return v / size; +} + +double reactor::get_energy_output() +{ + double v = 0; + + for(int i = 0; i < size; i++) + { + v += rods[i]->get_energy_output(); + } + + return v; +} + void reactor::get_stats(rod::val_t type, double& min, double& max) { min = INFINITY; diff --git a/src/reactor/reactor.hpp b/src/reactor/reactor.hpp index 00c36a3..05c1925 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -26,9 +26,15 @@ struct reactor reactor(const reactor& r); reactor(reactor&& r); + void scram(); + void reset_rod_speed(); + void add_rod_speed(double a); void update(double secs); void get_stats(rod::val_t type, double& min, double& max); void get_rod_stats(int type, double& min, double& max); + double get_flux(); + double get_energy_output(); + double get_average(rod::val_t type); double get_total(rod::val_t type); int move_cursor(int d); void toggle_selected(); diff --git a/src/reactor/rod.cpp b/src/reactor/rod.cpp index 29ae14d..cc30173 100644 --- a/src/reactor/rod.cpp +++ b/src/reactor/rod.cpp @@ -6,6 +6,9 @@ using namespace sim::reactor; +// Avogadro's Number +static double N_a = 6.02214076e23; + double rod::get(val_t type) const { return vals[type]; @@ -29,8 +32,8 @@ double rod::extract(val_t type, double s, double k, double o) } double v = m * 0.5 * (get(type) - o); - vals[type] -= v; + return v; } @@ -38,22 +41,44 @@ void rod::interact(rod* o, double secs) { for(int i = 0; i < rod::VAL_N; i++) { - val_t v = (val_t)i; - add(v, o->extract(v, secs, get_k(v), get(v))); + val_t t = (val_t)i; + double v = o->extract(t, secs, get_k(t), get(t)); + add(t, v); + + double v2 = std::abs(v / secs); + o->vals_n[t] += v2; + vals_n[t] += v2; } } +double rod::get_flux() const +{ + return (vals_n[val_t::N_FAST] + vals_n[val_t::N_SLOW]) * N_a / (get_side_area() * 10000) / 4; +} + double rod::get_volume() const { auto r = (sim::reactor::reactor*)reactor; return r->cell_width * r->cell_width * r->cell_height; } +double rod::get_side_area() const +{ + auto r = (sim::reactor::reactor*)reactor; + return r->cell_width * r->cell_height; +} + void rod::update_rod(double secs) { // decay the free neutrons double m = std::pow(0.5, secs / 879.4); vals[val_t::N_FAST] *= m; vals[val_t::N_SLOW] *= m; + + // clear data + for(int i = 0; i < rod::VAL_N; i++) + { + vals_n[(val_t)i] = 0; + } } diff --git a/src/reactor/rod.hpp b/src/reactor/rod.hpp index db714d7..32e27fe 100644 --- a/src/reactor/rod.hpp +++ b/src/reactor/rod.hpp @@ -12,14 +12,15 @@ class rod { public: + bool selected = false; void* reactor = nullptr; - static const int VAL_N = 3; + static const int VAL_N = 4; enum val_t { HEAT = 0, N_SLOW = 1, - N_FAST = 2 + N_FAST = 2, }; virtual ~rod() {}; @@ -30,16 +31,19 @@ public: virtual double get(val_t type) const; virtual std::unique_ptr clone() const { return std::make_unique(*this); } virtual glm::vec4 get_colour() const { return {0, 0, 0, 0}; } + virtual double get_energy_output() const { return 0; } virtual int get_id() const { return 0; } virtual bool has_sensors(val_t t) const { return false; } virtual bool should_display() const { return false; } virtual bool should_select() const { return false; } virtual void update_selected(double a) { } + + double get_flux() const; + double get_side_area() const; double get_volume() const; constexpr void toggle_selected() { selected = !selected; } - constexpr bool is_selected() const { return selected; } friend std::ostream& operator<<(std::ostream& o, const rod& r) { @@ -59,7 +63,7 @@ public: protected: double vals[VAL_N] = {0}; - bool selected = false; + double vals_n[VAL_N] = {0}; virtual void display(std::ostream& o) const { }; virtual double get_k(val_t type) const { return 0; } diff --git a/src/system.cpp b/src/system.cpp index e110c26..9bea50c 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -36,10 +36,7 @@ system::system() }; vessel = std::make_unique(8, 10, 300, sim::coolant::WATER); - reactor = std::make_unique(sim::reactor::builder(19, 19, 0.25, 8, - reactor::fuel::fuel_rod(0.5), - reactor::control::boron_rod(*vessel.get(), 1), - *vessel.get(), layout)); + reactor = std::make_unique(sim::reactor::builder(19, 19, 1.0 / 4.0, 4, reactor::fuel::fuel_rod(0.5), *vessel.get(), layout)); valve = std::make_unique>(*vessel.get(), 1, 500); pump = std::make_unique>(*vessel.get(), 1e4, 15); @@ -55,7 +52,7 @@ system::system(system&& o) void system::update(double dt) { - dt *= 10; + dt *= speed; vessel->update(dt); reactor->update(dt); valve->update(dt); diff --git a/src/system.hpp b/src/system.hpp index 058a91f..81b5436 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -21,6 +21,7 @@ struct system std::unique_ptr> valve; std::unique_ptr> pump; sim::graphics::mesh scene; + double speed = 1; system(); system(system&& o); From b1576166872b23bca1b22c9e244c6fa77ff22c4d Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Fri, 2 Feb 2024 22:03:47 +1100 Subject: [PATCH 35/43] adding other plant supporting equipment --- src/coolant/condenser.cpp | 40 ++++++++++ src/coolant/condenser.hpp | 24 ++++++ src/coolant/fluid_holder.cpp | 130 +++++++++++++++++++++++++++++++++ src/coolant/fluid_holder.hpp | 46 ++++++++++++ src/coolant/pump.cpp | 72 ++++++++++++++++++ src/coolant/pump.hpp | 57 +++++---------- src/coolant/valve.cpp | 27 +++++++ src/coolant/valve.hpp | 48 +++--------- src/electric/turbine.cpp | 0 src/electric/turbine.hpp | 10 +++ src/reactor/coolant/vessel.cpp | 114 +++-------------------------- src/reactor/coolant/vessel.hpp | 32 ++------ src/system.cpp | 19 +++-- src/system.hpp | 6 +- 14 files changed, 407 insertions(+), 218 deletions(-) create mode 100644 src/coolant/condenser.cpp create mode 100644 src/coolant/condenser.hpp create mode 100644 src/coolant/fluid_holder.cpp create mode 100644 src/coolant/fluid_holder.hpp create mode 100644 src/coolant/pump.cpp create mode 100644 src/coolant/valve.cpp create mode 100644 src/electric/turbine.cpp create mode 100644 src/electric/turbine.hpp diff --git a/src/coolant/condenser.cpp b/src/coolant/condenser.cpp new file mode 100644 index 0000000..3692c73 --- /dev/null +++ b/src/coolant/condenser.cpp @@ -0,0 +1,40 @@ + +#include "condenser.hpp" + +#include +#include + +using namespace sim::coolant; + +constexpr static double calc_cylinder(double h, double d) +{ + double r = d / 2; + + return M_PI * r * r * h; +} + +condenser::condenser(fluid_t type, double height, double diameter, double level) : + height(height), diameter(diameter), fluid_holder(type, calc_cylinder(height, diameter)) +{ + this->level = level; +} + +double condenser::get_bubble_hl() +{ + return (level / volume) * diameter * 0.5 / fluid.bubble_speed; +} + +void condenser::update(double secs) +{ + std::cout << "Condenser:\n\n"; + std::cout << "Level: " << level << "\n"; + std::cout << "Volume: " << volume << "\n"; + std::cout << "Height: " << height << "\n"; + std::cout << "Diameter: " << diameter << "\n"; + std::cout << "Pressure: " << get_pressure() << "\n"; + std::cout << "Teperature: " << heat << "\n\n"; + + ((sim::coolant::fluid_holder*)this)->update(secs); +} + + diff --git a/src/coolant/condenser.hpp b/src/coolant/condenser.hpp new file mode 100644 index 0000000..cbd8414 --- /dev/null +++ b/src/coolant/condenser.hpp @@ -0,0 +1,24 @@ + +#pragma once + +#include "fluid_holder.hpp" + +namespace sim::coolant +{ + +class condenser : public fluid_holder +{ + const double height; + const double diameter; + + virtual double get_bubble_hl(); + +public: + + condenser(fluid_t type, double height, double diameter, double level); + + void update(double dt); +}; + +}; + diff --git a/src/coolant/fluid_holder.cpp b/src/coolant/fluid_holder.cpp new file mode 100644 index 0000000..9682663 --- /dev/null +++ b/src/coolant/fluid_holder.cpp @@ -0,0 +1,130 @@ + +#include "fluid_holder.hpp" +#include "../constants.hpp" +#include "../conversions/temperature.hpp" +#include "../reactor/fuel/half_life.hpp" + +#include + +using namespace sim::coolant; + +fluid_holder::fluid_holder(fluid_t fluid, double volume) : fluid(fluid), volume(volume) +{ + +} + +double fluid_holder::add_heat(double m1, double t1) +{ + double t2 = get_heat(); + double t = t1 - t2; + double m2 = (fluid.l_to_g(level) + steam) * fluid.jPgk; + double m = m1 + m2; + + heat = t1 - t * m2 / m; + return heat; +} + +double fluid_holder::add_fluid(double v2, double t2) +{ + double v1 = level; + double t1 = get_heat(); + double t = t1 - t2; + + if(level + v2 > volume) + { + v2 = volume - level; + } + + heat = t1 - t * v2 / (v1 + v2); + level += v2; + + return v2; +} + +double fluid_holder::extract_fluid(double amount) +{ + if(amount < level) + { + level -= amount; + } + + else + { + amount = level; + level = 0; + } + + return amount; +} + +double fluid_holder::extract_steam(double dt, double a, double p2) +{ + // calculate the mass moved + double p1 = get_pressure(); + double p = (p1 - p2) * 0.001; // mPa or g/m/s^2 + + if(p == 0) + { + return 0; + } + + double V = (volume - level) * 0.001; // m^3 + double mass = std::min(dt * a * p / std::sqrt( V * std::abs(p) / steam ), steam); + + if(std::isnan(mass)) + { + return 0; + } + + steam -= mass; + return mass; +} + +double fluid_holder::get_pressure() const +{ + double T = conversions::temperature::c_to_k(heat); + double V = (volume - level) * 0.001; + double n = fluid.g_to_mol(steam); + + return (n * T * constants::R) / V; +} + +void fluid_holder::update(double secs) +{ + double V = (volume - level) * 0.001; + double P = fluid.vapor_pressure.calc_p(heat); + double T = conversions::temperature::c_to_k(heat); + double n = fluid.mol_to_g((V * P) / (T * constants::R)) - steam; + + double s = steam + n; + double l = fluid.l_to_g(level) - n; + double v = fluid.l_to_g(volume); + + if(l < 0) + { + s += l; + l = 0; + } + + if(s > v) + { + s = v; + l = 0; + } + + if(l > v) + { + l = v; + s = 0; + } + + double diff = s - steam; + + steam = s; + level = fluid.g_to_l(l); + heat -= diff * fluid.jPg / (fluid.l_to_g(level) + steam) / fluid.jPgk; + + if(diff > 0) steam_suspended += diff; + steam_suspended *= reactor::fuel::half_life::get(secs, get_bubble_hl()); +} + diff --git a/src/coolant/fluid_holder.hpp b/src/coolant/fluid_holder.hpp new file mode 100644 index 0000000..68bcd2e --- /dev/null +++ b/src/coolant/fluid_holder.hpp @@ -0,0 +1,46 @@ + +#pragma once + +#include "fluid_t.hpp" + +namespace sim::coolant +{ + +class fluid_holder +{ +protected: + + const fluid_t fluid; + const double volume; // litres + + double level = 0; // litres + double steam = 0; // grams + double steam_suspended = 0; // grams + double heat = 0; // celsius + + fluid_holder(fluid_t fluid, double volume); + +public: + + double add_heat(double m, double t); + double add_fluid(double amount, double heat); + double extract_steam(double dt, double a, double p2); + double extract_fluid(double amount); + + constexpr double get_volume() const { return volume; } // litres + constexpr double get_level() const { return level; } // litres + constexpr double get_heat() const { return heat; } // celsius + constexpr double get_steam() const { return steam; } // grams + constexpr double get_mass() const { return fluid.l_to_g(level) + steam; } // grams + constexpr double get_steam_density() const { return steam / (volume - level); } // g/L + constexpr double get_steam_suspended() const { return steam_suspended; } // grams + constexpr double get_void_ratio() const { double s = steam_suspended / get_steam_density(); return s / (level + s); } + + virtual double get_bubble_hl() = 0; + + double get_pressure() const; // pascals + void update(double dt); +}; + +}; + diff --git a/src/coolant/pump.cpp b/src/coolant/pump.cpp new file mode 100644 index 0000000..4bf4af3 --- /dev/null +++ b/src/coolant/pump.cpp @@ -0,0 +1,72 @@ + +#include "pump.hpp" + +#include +#include + +using namespace sim::coolant; + +pump::pump(fluid_holder& src, fluid_holder& dst, double mass, double radius, double l_per_rev, double friction) : + src(&src), dst(&dst), mass(mass), radius(radius), l_per_rev(l_per_rev), friction(friction) +{ + power = 1e3; +} + +double pump::get_flow() const +{ + return l_per_rev * get_rpm() / 60; +} + +double pump::get_rpm() const +{ + return velocity / (M_PI * mass * 0.001 * radius * radius); +} + +static double calc_work(double j, double mass) +{ + double m = 1; + + if(j < 0) + { + m = -1; + } + + return m * std::sqrt(m * j / (mass * 0.001)); +} + +void pump::update(double dt) +{ + velocity += calc_work(dt * power, mass); + + double src_heat = src->get_heat(); + double p_diff_1 = dst->get_pressure() - src->get_pressure(); + double src_volume = src->extract_fluid(get_flow() * dt); + double dst_volume = dst->add_fluid(src_volume, src_heat); + + if(dst_volume < src_volume) + { + src->add_fluid(src_volume - dst_volume, src_heat); + } + + double p_diff_2 = dst->get_pressure() - src->get_pressure(); + double p_diff = (p_diff_1 + p_diff_2) / 2; + double work = p_diff * dst_volume + get_rpm() * 60 * dt * friction; + + velocity -= calc_work(work, mass); + + if(dst->get_level() > 400 || src->get_level() < 50) + { + power = 0; + } + + else + { + power = 1e3; + } + + std::cout << "RPM: " << get_rpm() << "\t"; + std::cout << "Flow: " << get_flow() << std::endl; + std::cout << "Work Done: " << work << " J\n"; + std::cout << "Src Volume: " << src_volume << "\n"; +} + diff --git a/src/coolant/pump.hpp b/src/coolant/pump.hpp index a1f928c..e7ed378 100644 --- a/src/coolant/pump.hpp +++ b/src/coolant/pump.hpp @@ -1,57 +1,34 @@ #pragma once -#include +#include "fluid_holder.hpp" namespace sim::coolant { -template class pump { - const double max; - const double heat; - - A* a; - double rate = 0; + fluid_holder* const src; + fluid_holder* const dst; + + const double mass; // grams + const double radius; // meters + const double l_per_rev; // litres + const double friction; // J/rev + + double velocity = 0; // m/s public: - constexpr pump(A& a, double max, double heat) : a(&a), max(max), heat(heat) - { + double power = 0; // watts - } + pump(fluid_holder& src, fluid_holder& dst, double mass, double radius, double l_per_rev, double friction); - constexpr double get_rate() const - { - return rate; - } + double get_flow() const; // L/s + double get_rpm() const; // rev/min + + void update(double dt); +}; - constexpr double get_flow() const - { - return rate * max; - } - - constexpr void change_speed(double amount) - { - rate += amount; - - if(rate < 0) rate = 0; - if(rate > 1) rate = 1; - } - - void update(double secs) - { - a->add_fluid(rate * max * secs, heat); - } - - friend std::ostream& operator<<(std::ostream& o, const pump& p) - { - o << "Rate: " << (p.get_rate() * 100) << " %\n"; - o << "Flow: " << (p.get_flow() * 0.001) << " kg/s\n"; - return o; - } -}; - }; diff --git a/src/coolant/valve.cpp b/src/coolant/valve.cpp new file mode 100644 index 0000000..8d19958 --- /dev/null +++ b/src/coolant/valve.cpp @@ -0,0 +1,27 @@ + +#include "valve.hpp" + +using namespace sim::coolant; + +valve::valve(fluid_holder& src, fluid_holder& dst, double max) : src(&src), dst(&dst), max(max) +{ + +} + +void valve::set_state(double v) +{ + if(v > 1) v = 1; + if(v < 0) v = 0; + state = v; +} + +void valve::open(double v) +{ + set_state(state + v); +} + +void valve::update(double dt) +{ +// rate = a->extract_steam(dt, state * max, pressure) / dt; TODO +} + diff --git a/src/coolant/valve.hpp b/src/coolant/valve.hpp index 8498832..1339b49 100644 --- a/src/coolant/valve.hpp +++ b/src/coolant/valve.hpp @@ -1,55 +1,29 @@ #pragma once +#include "fluid_holder.hpp" + namespace sim::coolant { -template class valve { - A* a; - const double max; - const double pressure; + + fluid_holder* const src; + fluid_holder* const dst; double state = 0; - double rate = 0; public: - constexpr valve(A& a, double max, double pressure) : a(&a), max(max), pressure(pressure) - { + valve(fluid_holder& src, fluid_holder& dst, double max); - } - - constexpr double get_state() const - { - return state; - } - - constexpr void set_state(double v) - { - if(v > 1) v = 1; - if(v < 0) v = 0; - state = v; - } - - constexpr void open(double v) - { - set_state(state + v); - } - - constexpr void update(double secs) - { - rate = a->extract_steam(secs, state * max, pressure) / secs; - } - - friend std::ostream& operator<<(std::ostream& o, const valve& v) - { - o << "Opened: " << (v.state * 100) << " %\n"; - o << "Rate: " << v.rate << " g/s\n"; - return o; - } + void open(double v); + void update(double secs); + void set_state(double v); + + constexpr double get_state() const { return state; } }; }; diff --git a/src/electric/turbine.cpp b/src/electric/turbine.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/electric/turbine.hpp b/src/electric/turbine.hpp new file mode 100644 index 0000000..cf806ae --- /dev/null +++ b/src/electric/turbine.hpp @@ -0,0 +1,10 @@ + +#pragma once + +namespace sim::electric +{ + + + +}; + diff --git a/src/reactor/coolant/vessel.cpp b/src/reactor/coolant/vessel.cpp index 0c82ffe..b594669 100644 --- a/src/reactor/coolant/vessel.cpp +++ b/src/reactor/coolant/vessel.cpp @@ -15,114 +15,20 @@ constexpr static double calc_cylinder(double h, double d) return M_PI * r * r * h; } -vessel::vessel(double height, double diameter, double level, sim::coolant::fluid_t fluid) : - height(height), - diameter(diameter), - volume(calc_cylinder(height, diameter)), - fluid(fluid), - level(level), - bubble_hl(height * 0.5 / fluid.bubble_speed) +vessel::vessel(sim::coolant::fluid_t fluid, double height, double diameter, double level) : + sim::coolant::fluid_holder(fluid, calc_cylinder(height, diameter)), + height(height), diameter(diameter) { - + this->level = level; +} + +double vessel::get_bubble_hl() +{ + return (level / volume) * height * 0.5 / fluid.bubble_speed; } void vessel::update(double secs) { - double V = (volume - level) * 0.001; - double P = fluid.vapor_pressure.calc_p(heat); - double T = conversions::temperature::c_to_k(heat); - double n = fluid.mol_to_g((V * P) / (T * constants::R)) - steam; - - double s = steam + n; - double l = fluid.l_to_g(level) - n; - double v = fluid.l_to_g(volume); - - if(l < 0) - { - s += l; - l = 0; - } - - if(s > v) - { - s = v; - l = 0; - } - - if(l > v) - { - l = v; - s = 0; - } - - double diff = s - steam; - - steam = s; - level = fluid.g_to_l(l); - heat -= diff * fluid.jPg / (fluid.l_to_g(level) + steam) / fluid.jPgk; - - if(diff > 0) steam_suspended += diff; - steam_suspended *= fuel::half_life::get(secs, bubble_hl); -} - -double vessel::add_heat(double m1, double t1) -{ - double t2 = get_heat(); - double t = t1 - t2; - double m2 = (fluid.l_to_g(level) + steam) * fluid.jPgk; - double m = m1 + m2; - - heat = t1 - t * m2 / m; - return heat; -} - -double vessel::add_fluid(double m2, double t2) -{ - double m1 = get_mass(); - double t1 = get_heat(); - double t = t1 - t2; - - m2 = fluid.g_to_l(m2); - - if(level + m2 > volume) - { - m2 = volume - level; - } - - heat = t1 - t * m2 / (m1 + m2); - level += m2; - return m2; -} - -double vessel::extract_steam(double dt, double a, double p2) -{ - // calculate the mass moved - double p1 = get_pressure(); - double p = (p1 - p2) * 0.001; // mPa or g/m/s^2 - - if(p == 0) - { - return 0; - } - - double V = (volume - level) * 0.001; // m^3 - double mass = std::min(dt * a * p / std::sqrt( V * std::abs(p) / steam ), steam); - - if(std::isnan(mass)) - { - return 0; - } - - steam -= mass; - return mass; -} - -double vessel::get_pressure() const -{ - double T = conversions::temperature::c_to_k(heat); - double V = (volume - level) * 0.001; - double n = fluid.g_to_mol(steam); - - return (n * T * constants::R) / V; + ((sim::coolant::fluid_holder*)this)->update(secs); } diff --git a/src/reactor/coolant/vessel.hpp b/src/reactor/coolant/vessel.hpp index 99ddcac..4533075 100644 --- a/src/reactor/coolant/vessel.hpp +++ b/src/reactor/coolant/vessel.hpp @@ -3,44 +3,22 @@ #include -#include "../../coolant/fluid_t.hpp" +#include "../../coolant/fluid_holder.hpp" namespace sim::reactor::coolant { -class vessel +class vessel : public sim::coolant::fluid_holder { - double level; // litres - double heat = 0; // celsius - double steam = 0; // grams - double steam_suspended = 0; // grams - public: const double height; // meters const double diameter; // meters - const double volume; // litres - const double bubble_hl; // seconds - const sim::coolant::fluid_t fluid; - - vessel(double height, double diameter, double level, sim::coolant::fluid_t fluid); + vessel(sim::coolant::fluid_t fluid, double height, double diameter, double level); + virtual double get_bubble_hl(); void update(double secs); - double add_heat(double m, double t); - double add_fluid(double amount, double heat); - double extract_steam(double dt, double a, double p2); - - constexpr double get_volume() const { return volume; } // litres - constexpr double get_level() const { return level; } // litres - constexpr double get_heat() const { return heat; } // celsius - constexpr double get_steam() const { return steam; } // grams - constexpr double get_mass() const { return fluid.l_to_g(level) + steam; } // grams - constexpr double get_steam_density() const { return steam / (volume - level); } // g/L - constexpr double get_steam_suspended() const { return steam_suspended; } // grams - constexpr double get_void_ratio() const { double s = steam_suspended / get_steam_density(); return s / (level + s); } - - double get_pressure() const; // pascals friend std::ostream& operator<<(std::ostream& o, const vessel& v) { @@ -56,5 +34,5 @@ public: }; -} +}; diff --git a/src/system.cpp b/src/system.cpp index 9bea50c..f8495bb 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -35,19 +35,21 @@ system::system() " C C C C " }; - vessel = std::make_unique(8, 10, 300, sim::coolant::WATER); - reactor = std::make_unique(sim::reactor::builder(19, 19, 1.0 / 4.0, 4, reactor::fuel::fuel_rod(0.5), *vessel.get(), layout)); + vessel = std::make_unique(sim::coolant::WATER, 8, 10, 300); + reactor = std::make_unique(sim::reactor::builder(19, 19, 1.0 / 4.0, 4, reactor::fuel::fuel_rod(0.5), *vessel.get(), layout)); + condenser = std::make_unique(sim::coolant::WATER, 8, 6, 200); - valve = std::make_unique>(*vessel.get(), 1, 500); - pump = std::make_unique>(*vessel.get(), 1e4, 15); + turbine_bypass_valve = std::make_unique(*vessel.get(), *condenser.get(), 1); + core_pump = std::make_unique(*condenser.get(), *vessel.get(), 1e6, 1, 100, 100); } system::system(system&& o) { vessel = std::move(o.vessel); reactor = std::move(o.reactor); - valve = std::move(o.valve); - pump = std::move(o.pump); + condenser = std::move(o.condenser); + turbine_bypass_valve = std::move(o.turbine_bypass_valve); + core_pump = std::move(o.core_pump); } void system::update(double dt) @@ -55,7 +57,8 @@ void system::update(double dt) dt *= speed; vessel->update(dt); reactor->update(dt); - valve->update(dt); - pump->update(dt); + condenser->update(dt); + turbine_bypass_valve->update(dt); + core_pump->update(dt); } diff --git a/src/system.hpp b/src/system.hpp index 81b5436..db4405d 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -7,6 +7,7 @@ #include "reactor/reactor.hpp" #include "coolant/pump.hpp" #include "coolant/valve.hpp" +#include "coolant/condenser.hpp" #include "graphics/mesh/mesh.hpp" namespace sim @@ -18,8 +19,9 @@ struct system std::unique_ptr reactor; std::unique_ptr vessel; - std::unique_ptr> valve; - std::unique_ptr> pump; + std::unique_ptr condenser; + std::unique_ptr core_pump; + std::unique_ptr turbine_bypass_valve; sim::graphics::mesh scene; double speed = 1; From 45c40d88431271ad21761df3677290e8e4fa27e5 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Fri, 2 Feb 2024 22:10:18 +1100 Subject: [PATCH 36/43] temporarily disable new features --- src/system.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/system.cpp b/src/system.cpp index f8495bb..8ee6dac 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -57,8 +57,8 @@ void system::update(double dt) dt *= speed; vessel->update(dt); reactor->update(dt); - condenser->update(dt); - turbine_bypass_valve->update(dt); - core_pump->update(dt); +// condenser->update(dt); +// turbine_bypass_valve->update(dt); +// core_pump->update(dt); } From 309025675a424a71270c5a5362cdc2cef6158b2e Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Fri, 2 Feb 2024 22:31:28 +1100 Subject: [PATCH 37/43] freed the cursor while camera is locked --- src/graphics/input/focus.cpp | 43 ++++++++++++++++++++++++++++++++++-- src/graphics/input/focus.hpp | 4 +++- src/graphics/input/mouse.cpp | 20 ++++++++++++++++- src/graphics/input/mouse.hpp | 2 ++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/graphics/input/focus.cpp b/src/graphics/input/focus.cpp index 334ab2a..4b58b12 100644 --- a/src/graphics/input/focus.cpp +++ b/src/graphics/input/focus.cpp @@ -2,6 +2,7 @@ #include "focus.hpp" #include "../camera.hpp" #include "../../util/math.hpp" +#include "mouse.hpp" #include @@ -11,13 +12,23 @@ using namespace sim::graphics; static std::unique_ptr state = nullptr; +static bool mouse_visible = false; +static bool mouse_locked = false; static bool triggered = false; void focus::on_keypress(int key, int sc, int action, int mods) { if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { - state = nullptr; + if(is_mouse_locked()) + { + clear_mouse_locked(); + } + + else + { + mouse_locked = true; + } } if(state) @@ -59,6 +70,23 @@ void focus::update() { triggered = false; + bool locked = is_mouse_locked(); + + if(locked != mouse_visible) + { + if(locked) + { + mouse::show_cursor(); + } + + else + { + mouse::hide_cursor(); + } + + mouse_visible = locked; + } + if(state) { state->update(); @@ -70,11 +98,22 @@ bool focus::is_focused() return (state != nullptr); } -void focus::clear() +void focus::clear_focus() { state = nullptr; } +bool focus::is_mouse_locked() +{ + return is_focused() || mouse_locked; +} + +void focus::clear_mouse_locked() +{ + mouse_locked = false; + state = nullptr; +} + void focus::set(std::unique_ptr f) { state = std::move(f); diff --git a/src/graphics/input/focus.hpp b/src/graphics/input/focus.hpp index f5ce407..b641f30 100644 --- a/src/graphics/input/focus.hpp +++ b/src/graphics/input/focus.hpp @@ -16,9 +16,11 @@ struct focus_t virtual void update() { } }; -void clear(); bool is_focused(); +void clear_focus(); bool is_triggered(); +bool is_mouse_locked(); +void clear_mouse_locked(); void set(std::unique_ptr f); void on_keypress(int key, int sc, int action, int mods); void on_mouse_button(int button, int action, int mods); diff --git a/src/graphics/input/mouse.cpp b/src/graphics/input/mouse.cpp index 2c7059b..5059b6a 100644 --- a/src/graphics/input/mouse.cpp +++ b/src/graphics/input/mouse.cpp @@ -13,7 +13,7 @@ static double xpos = 0, ypos = 0; static void cb_cursor_pos(GLFWwindow* win, double x, double y) { - if(focus::is_focused()) + if(focus::is_mouse_locked()) { focus::on_cursor_pos(x - xpos, y - ypos); } @@ -38,6 +38,24 @@ void mouse::get(double& x, double& y) y = ypos; } +void mouse::show_cursor() +{ + double x, y; + GLFWwindow* win = window::get_window(); + glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + glfwGetCursorPos(win, &x, &y); + cb_cursor_pos(win, x, y); +} + +void mouse::hide_cursor() +{ + double x, y; + GLFWwindow* win = window::get_window(); + glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + glfwGetCursorPos(win, &x, &y); + cb_cursor_pos(win, x, y); +} + void mouse::init() { GLFWwindow* win = window::get_window(); diff --git a/src/graphics/input/mouse.hpp b/src/graphics/input/mouse.hpp index 20125d9..0534ead 100644 --- a/src/graphics/input/mouse.hpp +++ b/src/graphics/input/mouse.hpp @@ -6,6 +6,8 @@ namespace sim::graphics::mouse void init(); void get(double& x, double& y); +void show_cursor(); +void hide_cursor(); }; From d22259983632849bb66db0eaa9586897b25797f4 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sat, 3 Feb 2024 02:27:08 +1100 Subject: [PATCH 38/43] added clickable buttons :D --- assets/model/reactor_core_button1.stl | Bin 0 -> 184 bytes assets/model/reactor_core_button2.stl | Bin 0 -> 184 bytes assets/model/reactor_core_button3.stl | Bin 0 -> 184 bytes assets/model/reactor_core_button4.stl | Bin 0 -> 184 bytes assets/model/reactor_core_button5.stl | Bin 0 -> 184 bytes assets/model/reactor_core_button6.stl | Bin 0 -> 184 bytes assets/model/reactor_core_button7.stl | Bin 0 -> 184 bytes assets/model/reactor_core_button8.stl | Bin 0 -> 184 bytes assets/model/reactor_core_button9.stl | Bin 0 -> 184 bytes assets/model/reactor_core_joystick.stl | Bin 0 -> 2384 bytes assets/model/reactor_core_scram.stl | Bin 6284 -> 1584 bytes assets/unbaked/scene.blend | 4 +- src/graphics/input/focus.cpp | 65 ++++++++++--- src/graphics/input/focus.hpp | 5 + src/graphics/input/mouse.cpp | 5 + src/graphics/input/mouse.hpp | 3 + src/graphics/mesh/mesh.cpp | 5 +- src/graphics/monitor/core.cpp | 125 +++++++++++-------------- src/graphics/monitor/core.hpp | 5 +- src/graphics/window.cpp | 3 + src/graphics/window.hpp | 3 + src/reactor/control/boron_rod.cpp | 2 +- src/reactor/reactor.cpp | 2 +- 23 files changed, 138 insertions(+), 89 deletions(-) create mode 100644 assets/model/reactor_core_button1.stl create mode 100644 assets/model/reactor_core_button2.stl create mode 100644 assets/model/reactor_core_button3.stl create mode 100644 assets/model/reactor_core_button4.stl create mode 100644 assets/model/reactor_core_button5.stl create mode 100644 assets/model/reactor_core_button6.stl create mode 100644 assets/model/reactor_core_button7.stl create mode 100644 assets/model/reactor_core_button8.stl create mode 100644 assets/model/reactor_core_button9.stl create mode 100644 assets/model/reactor_core_joystick.stl diff --git a/assets/model/reactor_core_button1.stl b/assets/model/reactor_core_button1.stl new file mode 100644 index 0000000000000000000000000000000000000000..7e26a5c17871439f124dc94ecea2fbaf12d503d3 GIT binary patch literal 184 zcmZ>*D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIFrjE_u=lR}Xn)Hg-C>=(BA8t* ak_TpUU4Lv3V*D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIFrjE_u)mPW>~PB=-C>=(BA8t* ak_Tod==`*Yv5~c)>OfWnV#7>gU;qF=#U)b! literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_button3.stl b/assets/model/reactor_core_button3.stl new file mode 100644 index 0000000000000000000000000000000000000000..1cb9d8411646a85ba573fe252fb094fd96f34c34 GIT binary patch literal 184 zcmZ>*D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIFrjE_u&);3bGYS@?y$~X5zMX@ a$pf=*Ij}px*vMK?bs(z(v0)}LFaQ9cEFw(+ literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_button4.stl b/assets/model/reactor_core_button4.stl new file mode 100644 index 0000000000000000000000000000000000000000..7b94757f9f16dab1bb282baaf7988940ea52b5f5 GIT binary patch literal 184 zcmZ>*D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIFrjE_u=lR}Xm2nr#$lbiBA9(4 aGa1b0y8hT6#zxkHssmXShz&D|fdK&HVJJrc literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_button5.stl b/assets/model/reactor_core_button5.stl new file mode 100644 index 0000000000000000000000000000000000000000..a3539e2ac1ea6bb5d278faed3bd1b930d3e80360 GIT binary patch literal 184 zcmZ>*D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIFrjE_uustWX@4Oz**D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIFrjE_u)pQN?rOfWnV#7>gU;qI0`6CSg literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_button7.stl b/assets/model/reactor_core_button7.stl new file mode 100644 index 0000000000000000000000000000000000000000..d0f5dc501c875186e79b5bebcf150f446611789e GIT binary patch literal 184 zcmZ>*D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIFrjE_u;;q|*j{aagu^;_MKHVd abs(7SUH8!*#zxkHssmXShz&D|fdK#si75vF literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_button8.stl b/assets/model/reactor_core_button8.stl new file mode 100644 index 0000000000000000000000000000000000000000..7ce133016fe31ae38ec8e38532cd9f41d1af882e GIT binary patch literal 184 zcmZ>*D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIFrjE_uustWX|J|F!eO1eBADI! aIuOjhkjd--V*D9A4=Nlj5mE6UGRaLP%|OGz!#HPJKBGh!eIFrjE_u)pQN?x40m!eO1eBADI! aIuOjR7U6S%v5~c)>OfWnV#7>gU;qFku_Mg@ literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_joystick.stl b/assets/model/reactor_core_joystick.stl new file mode 100644 index 0000000000000000000000000000000000000000..94a9008ff5683d03a2048bd87a013a32791da9a5 GIT binary patch literal 2384 zcmbVOJ!q6c5FJpg)Jg=cQiz4<5sU~n`3^zSq!DcViHO`?rA9t1G&b6#i`XVru(T0W z0wUaJNEF0NC8V*{LWzx734${_`@Vh4o*-WJjyG@L&g{(aox6K`zP{7%O$_S!n-gbm z^l$b0^@&rR$<9gnpI=8s_1l^0jRW^9*T1O-gF$y?`H=p1yfvDwetp@jo_+0i4U0Tf z#rhZpM*;gu&(G|yWU;zRqMS+R?m7IL@=%YV&PSo(C}2NHGjDW0 zf`S=f%j4&dwYJhCTHQwHqfl@ZuzToyG&&za!3?l@JURZd?HCcAk4ERCP;eBmduX>e zIv=4EnE^JBsi_xLHXmaZ90i6G$J?i&;O%T(0tGzmh9bKkZMPoGa2{MYk*7;Z z!Hg-eOY9`fyh!)kYI`eYxOXu(ek=B1hNqUdM98D{V21Y<@5hiwt6;_ycyl`mZ{u}E zb#o@Y0oO2BFvE4@ij;~}#k5N}zg)o#=akP l6p0(IP;eBm&xh`I+q*%*46yf7dYkZFVtcnx@T>s4#~+`5az_9F literal 0 HcmV?d00001 diff --git a/assets/model/reactor_core_scram.stl b/assets/model/reactor_core_scram.stl index ddfcefd888539ce75a94d9518fc1633ec7c47da2..35f8899f989bbead8ae260f098eb7766fff649bd 100644 GIT binary patch literal 1584 zcmbVLJxc>Y6kOEC11lj#q9I=v7M?~$4JmFBf?%yE@mq6(pizQ_m7YJr-x07>e}LFU z#9D0B&i2w6IrHxB?apfFRSUDT^WMx{1ESuept}X?wz+6#x0s zUDC>8Vm9J>PNzR7?a!LLUdTErD=S+iH#`fGkv&U%?lNwffyjrSR*b&(uzCq6^BhDb ztzns~M|xg)NP72svEIRUVvaQsdA&->mcJX`dLB&4um0#PL}fj3#|%WK9+EO??Szc) zW>N;hR;=+>jTk|2;UGeccYjl9^tLx*22Mp9jL=j?E4oF~o|o5S2JcQ9h|uOQS8L%d za^z_+I)`~EcM&0Xix1WK-Hhxs`HTrt9*j_e%%Eho22O>@qn}e#UEb$mkM8EmT}1Nr zg4%riv!EOiVry9^h{$7UM7i6$Gy-{KS2Y6>VsL`=lFl5bLWHK`me91iSG>6po`VQ& z&P~L>K;I{ZiRga4MNDyg%Bf6BE}|7z-nSBq0$mRbRiTM z5ke(h*g`=eRd@a@%r{t)khQMFWreJ&WRZe(5$~BhXXZR69|XHP&ADgJd+*+N@B0QX z&CSkVoSN)7KR^3k$Ez2n-k+SB?|Sj{bEjVj|L5No6P;;n^}oBmSaW?x^2YE~a((5P z{onC-LnmtEttXPU-l+sQgbP1!^?$IBLZgWNOMid4Jka;xV&GAC5MD5O-RfR?oyT>z@y|Xu#GY@5m;c=|@R5IQ=k?-SqQ*`g6 z&?sW}7_%z;_m{1H>_Zu1cnlApNOf5W@p~{`M+8{#nbWK}8WqxxzhceyrL2$P|onru^=$-6v%qHb2&EepD11 zMeJX)`O&cXktviR#y&pYJudmM`O&cXQBi0Vv3pns8#X^Og)+qOSZFyeb=6FvQN-?H zm6+Q6$UKxGhR0IR6LLSADKv`MJ%;T2L-~zTh8P~}OYL&cle>fIr}?dA6uzZV#O`68 zA!TP!h8P|zN7{={6oPf1l--9>_?AWyyN7kSls!=yVt8z?w-p^Vgk}njB6bg(2C2=D ztWzsP43FEZZAmmgQkx$Yg+>ua9{uQv$`ErO6@)v+I#PC37%Xi6$RmmUuD}zz^mPQe~hrMJ{zGRdk zh6gINd|%mXDdlU4QTUcd5xYlzqiV>9GQ{w}eJtOJ%@i6%>>hb%$UKxGh6nme`QB}& z&?sW}*gSmnN9-Yldh~WiZ!h2KFszelTfh%kuuW(h$K*9GFzQlJ>yeeg|D!zBH+VWjeYU)u(#?JY&z=@PE06kr0 zbVhML7W*hNsEkf$eGQarV$SKV?-G3n#R_HgMW!#n5EdRi`jhMG*>3=zqP|7-?HhY2 z0|omPJlI!ag)&$b`v+EA-t$sZkA7ic=e(&ntMW#vB4)}vp*3B`<8+nLI@Sse;n3x_ zB7@3kRcgIKas3B(sz;eE9PKTqWO<=Zu=-YgEE>;%~Q6Im$?sRG;8sx zmph63m7cVoVF=&++EsWcqbGW>v(od?{e{r^TUU_}Wpqym+lQ|3V5?HU*u#DsH0-xQ z)`59G==#LyAKJA;u-^s^`)yEBXcV!}5BqJfX1@(Gg)+q6qsqGyg8eqYOTVJfSwid{ zlJk_`b;iF7%z5R1AA0y2Z&a!b&j(Sw^R7@2Wmt)bVqd8$lwr;h#okp_D8u_f1*Xsb z3Np+&qS%97v3d9!?_jD7Z!MzO=Ut&^r3|YbQJf{N(6fq}@)sTQgx{QnN3kkpSmB7` zOmv0qqsX8#>~M(U+;xSnN*UgBL~*uP70R&hQi18Szk&>JEu#2FsVbD=G(Z&JL9VcT z6lbLjZ!MzYTcA5vMk`-yx{S9Ol`5lktQDHgsA5&h0JAE!-t5a)+OnRwxA#$0iCSZE z^_1(XXSbI1qZOmIP{v~)%4q&Iv(cufpHBTXY@nIY1N2-kGMo&Fnl`?wLdWM)$ t-+u49da8_`=)ulPPeJz=uSyx+ldd*|?Y+ld;;NL<6&`F=>Ze}{;Xh4Ry{G^H diff --git a/assets/unbaked/scene.blend b/assets/unbaked/scene.blend index b894166..e58cc31 100644 --- a/assets/unbaked/scene.blend +++ b/assets/unbaked/scene.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de45f07d6afa36e19985970817efc827b060d18a7166350dfd27d2df8d1b120f -size 80493828 +oid sha256:66f728c728db0905aadf7f0052cb42cbaf72308100e3088e271af70d22ab3ec4 +size 80123820 diff --git a/src/graphics/input/focus.cpp b/src/graphics/input/focus.cpp index 4b58b12..d7674a2 100644 --- a/src/graphics/input/focus.cpp +++ b/src/graphics/input/focus.cpp @@ -1,16 +1,23 @@ -#include "focus.hpp" -#include "../camera.hpp" -#include "../../util/math.hpp" -#include "mouse.hpp" - -#include - #include #include +#include "focus.hpp" +#include "../../util/math.hpp" +#include "../window.hpp" +#include "../camera.hpp" +#include "../resize.hpp" +#include "mouse.hpp" + +#include + +#include + using namespace sim::graphics; +static glm::vec<3, double> trigger_near; +static glm::vec<3, double> trigger_far; + static std::unique_ptr state = nullptr; static bool mouse_visible = false; static bool mouse_locked = false; @@ -44,9 +51,28 @@ void focus::on_mouse_button(int button, int action, int mods) state->on_mouse_button(button, action, mods); } - else if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) + else if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) { triggered = true; + + if(is_mouse_locked()) + { + double mx, my; + mouse::get(mx, my); + + glm::vec2 wsize = resize::get_size(); + glm::vec4 viewport = glm::vec4(0, 0, wsize); + glm::vec2 mouse(mx, wsize.y - my); + + trigger_near = glm::unProject(glm::vec3(mouse, -1), camera::get_matrix(), window::projection_matrix, viewport); + trigger_far = glm::unProject(glm::vec3(mouse, 1), camera::get_matrix(), window::projection_matrix, viewport); + } + + else + { + trigger_near = camera::get_pos(); + trigger_far = trigger_near + camera::get_normal(); + } } } @@ -66,15 +92,30 @@ void focus::on_charcode(unsigned int c) } } +glm::vec<3, double> focus::get_trigger_near() +{ + return trigger_near; +} + +glm::vec<3, double> focus::get_trigger_far() +{ + return trigger_far; +} + void focus::update() { triggered = false; - bool locked = is_mouse_locked(); + bool c = is_mouse_locked(); - if(locked != mouse_visible) + if(state && !state->cursor_is_visible()) { - if(locked) + c = false; + } + + if(c != mouse_visible) + { + if(c) { mouse::show_cursor(); } @@ -84,7 +125,7 @@ void focus::update() mouse::hide_cursor(); } - mouse_visible = locked; + mouse_visible = c; } if(state) diff --git a/src/graphics/input/focus.hpp b/src/graphics/input/focus.hpp index b641f30..a586f0f 100644 --- a/src/graphics/input/focus.hpp +++ b/src/graphics/input/focus.hpp @@ -1,6 +1,8 @@ #pragma once +#include + #include namespace sim::graphics::focus @@ -9,6 +11,7 @@ namespace sim::graphics::focus struct focus_t { virtual ~focus_t() { } + virtual bool cursor_is_visible() { return true; } virtual void on_keypress(int key, int sc, int action, int mods) { } virtual void on_mouse_button(int button, int action, int mods) { } virtual void on_cursor_pos(double x, double y) { } @@ -21,6 +24,8 @@ void clear_focus(); bool is_triggered(); bool is_mouse_locked(); void clear_mouse_locked(); +glm::vec<3, double> get_trigger_near(); +glm::vec<3, double> get_trigger_far(); void set(std::unique_ptr f); void on_keypress(int key, int sc, int action, int mods); void on_mouse_button(int button, int action, int mods); diff --git a/src/graphics/input/mouse.cpp b/src/graphics/input/mouse.cpp index 5059b6a..4403e11 100644 --- a/src/graphics/input/mouse.cpp +++ b/src/graphics/input/mouse.cpp @@ -38,6 +38,11 @@ void mouse::get(double& x, double& y) y = ypos; } +glm::vec2 mouse::get() +{ + return {xpos, ypos}; +} + void mouse::show_cursor() { double x, y; diff --git a/src/graphics/input/mouse.hpp b/src/graphics/input/mouse.hpp index 0534ead..8d2dc86 100644 --- a/src/graphics/input/mouse.hpp +++ b/src/graphics/input/mouse.hpp @@ -1,10 +1,13 @@ #pragma once +#include + namespace sim::graphics::mouse { void init(); +glm::vec2 get(); void get(double& x, double& y); void show_cursor(); void hide_cursor(); diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp index db7fde6..bf9644e 100644 --- a/src/graphics/mesh/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -98,7 +98,10 @@ bool ray_intersects_triangle(vec3 ray_origin, bool mesh::check_focus(double len) const { - return focus::is_triggered() && check_intersect(camera::get_pos(), camera::get_normal() * len); + auto near = focus::get_trigger_near(); + auto far = focus::get_trigger_far(); + + return focus::is_triggered() && check_intersect(near, glm::normalize(far - near) * len); } bool mesh::check_focus() const diff --git a/src/graphics/monitor/core.cpp b/src/graphics/monitor/core.cpp index 6b2a0fe..3a04381 100644 --- a/src/graphics/monitor/core.cpp +++ b/src/graphics/monitor/core.cpp @@ -15,83 +15,42 @@ using namespace sim::graphics; using namespace sim::graphics::monitor; -struct core_focus_t : public focus::focus_t +struct core_joystick : public focus::focus_t { virtual void on_cursor_pos(double x, double y) { - sim::system::active.reactor->add_rod_speed(-y * 1e-6); + sim::system::active.reactor->add_rod_speed(y * 1e-6); } - void set_all(bool state) + virtual void on_mouse_button(int button, int action, int mods) { - for(int i = 0; i < sim::system::active.reactor->rods.size(); i++) + if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) { - sim::reactor::rod* r = sim::system::active.reactor->rods[i].get(); - - if(r->should_select()) - { - r->selected = state; - } + focus::clear_focus(); } } - void toggle_auto() + virtual bool cursor_is_visible() { - //TODO - } - - virtual void on_charcode(unsigned int c) - { - sim::system& sys = sim::system::active; - - switch(c) - { - case 'a': case 'A': - sys.reactor->move_cursor(-1); - break; - case 'd': case 'D': - sys.reactor->move_cursor(1); - break; - case 'w': case 'W': - sys.reactor->move_cursor(-sim::system::active.reactor->height); - break; - case 's': case 'S': - sys.reactor->move_cursor(sim::system::active.reactor->height); - break; - } - } - - virtual void on_keypress(int key, int sc, int action, int mods) - { - if(action != GLFW_PRESS) - { - return; - } - - switch(key) - { - case GLFW_KEY_Z: - sim::system::active.reactor->toggle_selected(); - break; - case GLFW_KEY_C: - toggle_auto(); - break; - case GLFW_KEY_X: - sim::system::active.reactor->reset_rod_speed(); - break; - case GLFW_KEY_Q: - set_all(true); - break; - case GLFW_KEY_E: - set_all(false); - break; - } + return false; } }; core::core() { +} +static void set_all(bool state) +{ + for(int i = 0; i < sim::system::active.reactor->rods.size(); i++) + { + sim::reactor::rod* r = sim::system::active.reactor->rods[i].get(); + + if(r->should_select()) + { + r->selected = state; + } + } } void core::init() @@ -110,22 +69,46 @@ void core::init() rmesh2.load_model("../assets/model/", "reactor_core_interface_cell.stl"); mesh2.bind(); mesh2.set(rmesh2, GL_STATIC_DRAW); - - mesh_click.load_model("../assets/model/", "reactor_core_input.stl"); - mesh_scram.load_model("../assets/model/", "reactor_core_scram.stl"); + + m_buttons[0].load_model("../assets/model/", "reactor_core_button1.stl"); + m_buttons[1].load_model("../assets/model/", "reactor_core_button2.stl"); + m_buttons[2].load_model("../assets/model/", "reactor_core_button3.stl"); + m_buttons[3].load_model("../assets/model/", "reactor_core_button4.stl"); + m_buttons[4].load_model("../assets/model/", "reactor_core_button5.stl"); + m_buttons[5].load_model("../assets/model/", "reactor_core_button6.stl"); + m_buttons[6].load_model("../assets/model/", "reactor_core_button7.stl"); + m_buttons[7].load_model("../assets/model/", "reactor_core_button8.stl"); + m_buttons[8].load_model("../assets/model/", "reactor_core_button9.stl"); + m_joystick.load_model("../assets/model/", "reactor_core_joystick.stl"); + m_scram.load_model("../assets/model/", "reactor_core_scram.stl"); } void core::update() { - if(mesh_click.check_focus()) - { - focus::set(std::make_unique()); - } - - if(mesh_scram.check_focus()) - { + sim::system& sys = sim::system::active; + + if(m_joystick.check_focus()) + focus::set(std::make_unique()); + if(m_scram.check_focus()) sim::system::active.reactor->scram(); - } + if(m_buttons[0].check_focus()) + set_all(true); + if(m_buttons[1].check_focus()) + sys.reactor->move_cursor(-sim::system::active.reactor->height); + if(m_buttons[2].check_focus()) + set_all(false); + if(m_buttons[3].check_focus()) + sys.reactor->move_cursor(-1); + if(m_buttons[4].check_focus()) + sys.reactor->move_cursor(sim::system::active.reactor->height); + if(m_buttons[5].check_focus()) + sys.reactor->move_cursor(1); + if(m_buttons[6].check_focus()) + sim::system::active.reactor->toggle_selected(); + if(m_buttons[7].check_focus()) + sim::system::active.reactor->reset_rod_speed(); +// if(m_buttons[8].check_focus()) +// } void core::render() diff --git a/src/graphics/monitor/core.hpp b/src/graphics/monitor/core.hpp index 677bc31..71653dd 100644 --- a/src/graphics/monitor/core.hpp +++ b/src/graphics/monitor/core.hpp @@ -9,8 +9,11 @@ namespace sim::graphics::monitor class core { - sim::graphics::mesh mesh_click, mesh_scram; sim::graphics::glmesh mesh1, mesh2; + + sim::graphics::mesh m_buttons[9]; + sim::graphics::mesh m_joystick; + sim::graphics::mesh m_scram; public: diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 8c9843d..abbfc1d 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -32,6 +32,8 @@ static glmesh MeshScene; static monitor::vessel MonitorVessel; static monitor::core MonitorCore; +glm::mat4 window::projection_matrix; + void GLAPIENTRY cb_debug_message(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { if(severity != GL_DEBUG_SEVERITY_NOTIFICATION) @@ -132,6 +134,7 @@ void window::render() glm::mat4 mat_projection = glm::perspective(glm::radians(90.0f), resize::get_aspect(), 0.01f, 20.f); glUniformMatrix4fv(shader::gl_projection, 1, false, &mat_projection[0][0]); glUniformMatrix4fv(shader::gl_camera, 1, false, &mat_camera[0][0]); + projection_matrix = mat_projection; glClearColor(0, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); diff --git a/src/graphics/window.hpp b/src/graphics/window.hpp index 8626cce..f30ae3e 100644 --- a/src/graphics/window.hpp +++ b/src/graphics/window.hpp @@ -2,12 +2,15 @@ #pragma once #include +#include #include "../system.hpp" namespace sim::graphics::window { +extern glm::mat4 projection_matrix; + bool should_close(); void create(); void update(double dt); diff --git a/src/reactor/control/boron_rod.cpp b/src/reactor/control/boron_rod.cpp index 12d893d..f7196ad 100644 --- a/src/reactor/control/boron_rod.cpp +++ b/src/reactor/control/boron_rod.cpp @@ -50,7 +50,7 @@ void boron_rod::update(double secs) void boron_rod::update_selected(double a) { - inserted += a; + inserted -= a; if(inserted > 1) inserted = 1; if(inserted < 0) inserted = 0; diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index 809dd02..ced8e84 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -58,7 +58,7 @@ void reactor::add_rod_speed(double a) void reactor::scram() { - rod_speed = 0.2; + rod_speed = -0.2; for(int i = 0; i < size; i++) { From 1306dd43ca021d66fd5f4cb010767a351ce383f9 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sat, 3 Feb 2024 18:12:18 +1100 Subject: [PATCH 39/43] added some primary coolant loop gui stuff --- CMakeLists.txt | 2 +- assets/model/reactor_core_joystick.stl | Bin 2384 -> 2384 bytes .../model/turbine_valve_bypass_joystick.stl | Bin 0 -> 2384 bytes assets/model/turbine_valve_inlet_joystick.stl | Bin 0 -> 2384 bytes assets/scene-baked.glb | 4 +- assets/unbaked/scene.blend | 4 +- assets/unbaked/scene/labels.png | 3 + assets/unbaked/scene/labels.xcf | 3 + src/coolant/condenser.cpp | 8 -- src/coolant/pump.cpp | 4 +- src/coolant/pump.hpp | 2 +- src/coolant/valve.cpp | 16 +-- src/coolant/valve.hpp | 7 +- src/display.cpp | 54 --------- src/display.hpp | 11 -- src/electric/turbine.cpp | 32 +++++ src/electric/turbine.hpp | 14 +++ src/graphics/monitor/core.cpp | 84 +++++++++---- src/graphics/monitor/core.hpp | 2 +- src/graphics/monitor/helpers.hpp | 14 +++ src/graphics/monitor/primary_loop.cpp | 113 ++++++++++++++++++ src/graphics/monitor/primary_loop.hpp | 25 ++++ src/graphics/monitor/vessel.cpp | 12 +- src/graphics/monitor/vessel.hpp | 1 - src/graphics/window.cpp | 5 + src/reactor/builder.cpp | 2 +- src/reactor/builder.hpp | 2 +- src/reactor/control/boron_rod.cpp | 2 +- src/reactor/control/boron_rod.hpp | 2 +- src/reactor/coolant/pipe.cpp | 4 +- src/reactor/coolant/pipe.hpp | 2 +- src/system.cpp | 16 ++- src/system.hpp | 3 + 33 files changed, 317 insertions(+), 136 deletions(-) create mode 100644 assets/model/turbine_valve_bypass_joystick.stl create mode 100644 assets/model/turbine_valve_inlet_joystick.stl create mode 100644 assets/unbaked/scene/labels.png create mode 100644 assets/unbaked/scene/labels.xcf delete mode 100644 src/display.cpp delete mode 100644 src/display.hpp create mode 100644 src/graphics/monitor/helpers.hpp create mode 100644 src/graphics/monitor/primary_loop.cpp create mode 100644 src/graphics/monitor/primary_loop.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a7fc048..3f0422a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.25) project(FastNuclearSim VERSION 1.0) set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_FLAGS "-O3 -lncurses -I/usr/include/freetype2") +set(CMAKE_CXX_FLAGS "-g -O3 -I/usr/include/freetype2") file(GLOB_RECURSE SOURCES src/*.cpp) diff --git a/assets/model/reactor_core_joystick.stl b/assets/model/reactor_core_joystick.stl index 94a9008ff5683d03a2048bd87a013a32791da9a5..9272a14b5a7a35cc28120edfcf2f532d98ad72c9 100644 GIT binary patch literal 2384 zcmbVMJ!lkB7=g@( zGr3JLNhK)Q#9Sc(dogHZ6Tf+HXLp{vmDk+BeDlq|?{DhC;zIX+XMXHPcj3<1vzf=t#agnnTUKZkaqu{0`S;BS?@)#q z9>4mV$;Ur4na62U{HRc96mjrCoxWMU;GqmLJo=+sRy%sUHO1!&g+>tvkG?5Jw_5{v zC_~J5oIiX$dj}M?3I)E3QN&TLQoaLGU>L=ItTe90wJIr4T}BZHkE_&5GlrIJFUh;xsDO}kepLky2w z#|H8a+mA|xMiJ+=N?9vqh~d$gUCk@)ob5+y`{BQeQOKoH#KB|DDtmOi<`v2i!{hkW z`+Q=YTekc==7T9=6mn@4aqzH7OgX#C5X0m0USIYD3eGO0kV~V8gNN;4${kdOnC~d| z+;%YK4l;`GU=+N8gU4fg`OxMUFIR?`{V49$x>ff`c1swAT&NqPh=T`mhhHzceVAR| zTFe;kG5i>f|6X*R(SIuFQC29!FGLh8QdTGf1#by5ap&W&QifBByt*r~LK)p!y%MoP z8NF9}A7h0wP_XC7#NNaTWwZxcL+4IDUws#Jy{OMxH?2tQp^R2R-{(Cp&_fx0lYVXC zad7Y`nk)Tw^)3{cD`nI#_ORQId*x@6dH)=)5dZ)H literal 2384 zcmbVOJ!q6c5FJpg)Jg=cQiz4<5sU~n`3^zSq!DcViHO`?rA9t1G&b6#i`XVru(T0W z0wUaJNEF0NC8V*{LWzx734${_`@Vh4o*-WJjyG@L&g{(aox6K`zP{7%O$_S!n-gbm z^l$b0^@&rR$<9gnpI=8s_1l^0jRW^9*T1O-gF$y?`H=p1yfvDwetp@jo_+0i4U0Tf z#rhZpM*;gu&(G|yWU;zRqMS+R?m7IL@=%YV&PSo(C}2NHGjDW0 zf`S=f%j4&dwYJhCTHQwHqfl@ZuzToyG&&za!3?l@JURZd?HCcAk4ERCP;eBmduX>e zIv=4EnE^JBsi_xLHXmaZ90i6G$J?i&;O%T(0tGzmh9bKkZMPoGa2{MYk*7;Z z!Hg-eOY9`fyh!)kYI`eYxOXu(ek=B1hNqUdM98D{V21Y<@5hiwt6;_ycyl`mZ{u}E zb#o@Y0oO2BFvE4@ij;~}#k5N}zg)o#=akP l6p0(IP;eBm&xh`I+q*%*46yf7dYkZFVtcnx@T>s4#~+`5az_9F diff --git a/assets/model/turbine_valve_bypass_joystick.stl b/assets/model/turbine_valve_bypass_joystick.stl new file mode 100644 index 0000000000000000000000000000000000000000..08e66409da848d661a9b45a3091af0d18c6ad4d6 GIT binary patch literal 2384 zcmbVOJ!lkB780W4Wti>w_56X;1b9|ZLP!+My_;uKdb{A?zhBCy+SWmKe z|75YuxbUqN9nDuAsv`Coc&G1nd%Tr0#K=enSv=*K@x~mVCpc6^>@(h(Le+uSrHa^Rlr=9h zlp#h2>c6p-gf*`?R7LDF%HAy;$`B(1eY~AAqwL*^Lsi5+?;!qW_&#;>3thqA8$k^EGj5@|S zt9j0vQ-!lsMeH-KpXud)-uvEFh8P*!X-DS6dN*h9QiZcrMeH+d26N7!GQ_;cc+M}| zSH0ZvQ%e=^K^3xreTMCloV!FBV%B54uWXm(+$B`uEW8_4#6APF=YRUa?nCc#*P_R; zkC8_;e)ogzj4q*r8C8ce{6eVsB2|YnaB!F4B+h*JRmy1Z>a2tgWprwFON0((bYJOy z3?0hA!J6YF)+Tf)qczYsbZ+os=dJhkgF5TG>5GIJ%IGWT{fdmM%uq(}q+dJEI6ZwD z^p$?QW{(}{D`hk<%&^^#eO2@%&;7UCwJS&as^U-;v7Zmy?L+=wq6{(mRcqtik!|Xa Q_O}o>q6%lJir8oT1-X9mPXGV_ literal 0 HcmV?d00001 diff --git a/assets/model/turbine_valve_inlet_joystick.stl b/assets/model/turbine_valve_inlet_joystick.stl new file mode 100644 index 0000000000000000000000000000000000000000..dfe90a2cfcdf5c4ac664e00e2d6b3921c16dc10f GIT binary patch literal 2384 zcmbVOJ!n%=7`+N2I0S?`bkG*X!NC}RY5mE23KHnzl0~#yOQQ&CYj6-V7)+z!(natO z9mGW(6cGm-^A>T?ignZ0!9gd7Vn-e7ciw#`J&%{kEIsg@bI<+$Zr|a{6XVrOm9hTQ z)$#NFht5?lj8&>zcjXKD9qxaAc@zD3S1La5sRht~oS6)Vr$)njcY6ckiwA~+okPv= z%J66i?A*fJQt^Ekg|diol5@xR=7O(t<=8_b#5nib%Vv1?)2YN`%U~{eGM`l_i`aW? zH1?vYkcUQy;c@eCGo1M|l6V~ZRu0w=WEILH_8!~U)q*#zQV|{+A%;h-=X$up(PNv{ z>Gy0_p)6wWF=&c|v!ymXG(yZgcCR{_;s zPT@r2vB|2tFql=Kx?~Z1kImMLr}O35LnFlSK>u$%8csY$Y)Zb&WfjUI_8#AD5 z>@imwA%;g^vmyIo(;gL)X(tP3DT~;9MQHdybRXo3uhB+5@ej zb5r+Que`1vZzs>vx@kqy9vaaqXnxL3KWOcthek9fz3sBc($dw`Thq|NUVvT&BN Hh`q-@zoiMR literal 0 HcmV?d00001 diff --git a/assets/scene-baked.glb b/assets/scene-baked.glb index 2d48ad4..5264c0c 100644 --- a/assets/scene-baked.glb +++ b/assets/scene-baked.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8e38fa332dfb5a4b589915a2b08cad209d2b25668a86881c1fbd50e90c389bf -size 69580444 +oid sha256:8ed870c17367c0bb8858bb68fbc5277f56e47c3ffa650591b11d703e3052da9f +size 59404508 diff --git a/assets/unbaked/scene.blend b/assets/unbaked/scene.blend index e58cc31..2334421 100644 --- a/assets/unbaked/scene.blend +++ b/assets/unbaked/scene.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66f728c728db0905aadf7f0052cb42cbaf72308100e3088e271af70d22ab3ec4 -size 80123820 +oid sha256:5ba3be1ad135735ed6ac111c2229004e8eb7d2d9442a873a5f49bd4cbc905f40 +size 10146216 diff --git a/assets/unbaked/scene/labels.png b/assets/unbaked/scene/labels.png new file mode 100644 index 0000000..7ae9d7b --- /dev/null +++ b/assets/unbaked/scene/labels.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22b0bcb35c11d4da7718d85136baadbc5801d9823661e1feecd8851d384ef290 +size 26579 diff --git a/assets/unbaked/scene/labels.xcf b/assets/unbaked/scene/labels.xcf new file mode 100644 index 0000000..823bd5d --- /dev/null +++ b/assets/unbaked/scene/labels.xcf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28973e2b620fe500c7b820ea036ad81f65ff0181a597a480b9bcdf8ee4a5fe46 +size 81896 diff --git a/src/coolant/condenser.cpp b/src/coolant/condenser.cpp index 3692c73..f3b6472 100644 --- a/src/coolant/condenser.cpp +++ b/src/coolant/condenser.cpp @@ -26,14 +26,6 @@ double condenser::get_bubble_hl() void condenser::update(double secs) { - std::cout << "Condenser:\n\n"; - std::cout << "Level: " << level << "\n"; - std::cout << "Volume: " << volume << "\n"; - std::cout << "Height: " << height << "\n"; - std::cout << "Diameter: " << diameter << "\n"; - std::cout << "Pressure: " << get_pressure() << "\n"; - std::cout << "Teperature: " << heat << "\n\n"; - ((sim::coolant::fluid_holder*)this)->update(secs); } diff --git a/src/coolant/pump.cpp b/src/coolant/pump.cpp index 4bf4af3..5851266 100644 --- a/src/coolant/pump.cpp +++ b/src/coolant/pump.cpp @@ -6,8 +6,8 @@ using namespace sim::coolant; -pump::pump(fluid_holder& src, fluid_holder& dst, double mass, double radius, double l_per_rev, double friction) : - src(&src), dst(&dst), mass(mass), radius(radius), l_per_rev(l_per_rev), friction(friction) +pump::pump(fluid_holder* src, fluid_holder* dst, double mass, double radius, double l_per_rev, double friction) : + src(src), dst(dst), mass(mass), radius(radius), l_per_rev(l_per_rev), friction(friction) { power = 1e3; } diff --git a/src/coolant/pump.hpp b/src/coolant/pump.hpp index e7ed378..261d7b9 100644 --- a/src/coolant/pump.hpp +++ b/src/coolant/pump.hpp @@ -22,7 +22,7 @@ public: double power = 0; // watts - pump(fluid_holder& src, fluid_holder& dst, double mass, double radius, double l_per_rev, double friction); + pump(fluid_holder* src, fluid_holder* dst, double mass, double radius, double l_per_rev, double friction); double get_flow() const; // L/s double get_rpm() const; // rev/min diff --git a/src/coolant/valve.cpp b/src/coolant/valve.cpp index 8d19958..79ed2e2 100644 --- a/src/coolant/valve.cpp +++ b/src/coolant/valve.cpp @@ -3,25 +3,25 @@ using namespace sim::coolant; -valve::valve(fluid_holder& src, fluid_holder& dst, double max) : src(&src), dst(&dst), max(max) +valve::valve(fluid_holder* src, fluid_holder* dst, double max) : src(src), dst(dst), max(max) { } -void valve::set_state(double v) +void valve::add_open_speed(double v) { - if(v > 1) v = 1; - if(v < 0) v = 0; - state = v; + open_speed += v; } -void valve::open(double v) +void valve::clear_open_speed() { - set_state(state + v); + open_speed = 0; } void valve::update(double dt) { -// rate = a->extract_steam(dt, state * max, pressure) / dt; TODO + state += open_speed * dt; + if(state > 1) state = 1; + if(state < 0) state = 0; } diff --git a/src/coolant/valve.hpp b/src/coolant/valve.hpp index 1339b49..45060f2 100644 --- a/src/coolant/valve.hpp +++ b/src/coolant/valve.hpp @@ -13,15 +13,16 @@ class valve fluid_holder* const src; fluid_holder* const dst; + double open_speed = 0; double state = 0; public: - valve(fluid_holder& src, fluid_holder& dst, double max); + valve(fluid_holder* src, fluid_holder* dst, double max); - void open(double v); void update(double secs); - void set_state(double v); + void add_open_speed(double v); + void clear_open_speed(); constexpr double get_state() const { return state; } }; diff --git a/src/display.cpp b/src/display.cpp deleted file mode 100644 index 2ea7ce5..0000000 --- a/src/display.cpp +++ /dev/null @@ -1,54 +0,0 @@ - -#include "display.hpp" - -#include -#include -#include - -void display::draw_text(int x, int y, const char* at) -{ - for(int i = 0;; i++) - { - const char* start = at; - char c = (at++)[0]; - - while(c != '\n' && c != '\0') - { - c = (at++)[0]; - } - - mvaddnstr(x + i, y, start, (size_t)(at - start)); - - if(c == '\0') - { - return; - } - } -} - -void display::draw_box(int x, int y, int h, int w) -{ - mvaddch(x, y, '+'); - - for(int i = 0; i < w - 2; i++) - { - addch('-'); - } - - addch('+'); - - for(int i = 0; i < h - 2; i++) - { - mvaddch(x + i + 1, y, '|'); - mvaddch(x + i + 1, y + w - 1, '|'); - } - - mvaddch(x + h - 1, y, '+'); - - for(int i = 0; i < w - 2; i++) - { - addch('-'); - } - - addch('+'); -} diff --git a/src/display.hpp b/src/display.hpp deleted file mode 100644 index be126e3..0000000 --- a/src/display.hpp +++ /dev/null @@ -1,11 +0,0 @@ - -#pragma once - -namespace display -{ - -void draw_text(int x, int y, const char* str); -void draw_box(int x, int y, int h, int w); - -} - diff --git a/src/electric/turbine.cpp b/src/electric/turbine.cpp index e69de29..286127a 100644 --- a/src/electric/turbine.cpp +++ b/src/electric/turbine.cpp @@ -0,0 +1,32 @@ + +#include "turbine.hpp" + +#include +#include + +using namespace sim::electric; + +constexpr static double calc_cylinder(double h, double d) +{ + double r = d / 2; + + return M_PI * r * r * h; +} + +turbine::turbine(coolant::fluid_t type, double height, double diameter, double level) : + height(height), diameter(diameter), sim::coolant::fluid_holder(type, calc_cylinder(height, diameter)) +{ + this->level = level; +} + +double turbine::get_bubble_hl() +{ + return (level / volume) * diameter * 0.5 / fluid.bubble_speed; +} + +void turbine::update(double secs) +{ + ((sim::coolant::fluid_holder*)this)->update(secs); +} + + diff --git a/src/electric/turbine.hpp b/src/electric/turbine.hpp index cf806ae..aa9afc5 100644 --- a/src/electric/turbine.hpp +++ b/src/electric/turbine.hpp @@ -1,10 +1,24 @@ #pragma once +#include "../coolant/fluid_holder.hpp" + namespace sim::electric { +class turbine : public sim::coolant::fluid_holder +{ + const double height; + const double diameter; + + virtual double get_bubble_hl(); +public: + + turbine(coolant::fluid_t type, double height, double diameter, double level); + + void update(double dt); +}; }; diff --git a/src/graphics/monitor/core.cpp b/src/graphics/monitor/core.cpp index 3a04381..0604e25 100644 --- a/src/graphics/monitor/core.cpp +++ b/src/graphics/monitor/core.cpp @@ -3,9 +3,11 @@ #include #include "core.hpp" +#include "helpers.hpp" #include "../locations.hpp" #include "../input/focus.hpp" #include "../mesh/arrays.hpp" +#include "../../system.hpp" #include @@ -15,6 +17,60 @@ using namespace sim::graphics; using namespace sim::graphics::monitor; +static void set_all(bool state) +{ + for(int i = 0; i < sim::system::active.reactor->rods.size(); i++) + { + sim::reactor::rod* r = sim::system::active.reactor->rods[i].get(); + + if(r->should_select()) + { + r->selected = state; + } + } +} + +struct core_monitor : public focus::focus_t +{ + virtual void on_keypress(int key, int sc, int action, int mods) + { + if(action != GLFW_PRESS) + { + return; + } + + sim::system& sys = sim::system::active; + + switch(key) + { + case GLFW_KEY_KP_7: + set_all(true); + break; + case GLFW_KEY_KP_8: + sys.reactor->move_cursor(-sys.reactor->height); + break; + case GLFW_KEY_KP_9: + set_all(false); + break; + case GLFW_KEY_KP_4: + sys.reactor->move_cursor(-1); + break; + case GLFW_KEY_KP_5: + sys.reactor->toggle_selected(); + break; + case GLFW_KEY_KP_6: + sys.reactor->move_cursor(1); + break; + case GLFW_KEY_KP_1: + sys.reactor->reset_rod_speed(); + break; + case GLFW_KEY_KP_2: + sys.reactor->move_cursor(sys.reactor->height); + break; + } + } +}; + struct core_joystick : public focus::focus_t { virtual void on_cursor_pos(double x, double y) @@ -40,19 +96,6 @@ core::core() { } -static void set_all(bool state) -{ - for(int i = 0; i < sim::system::active.reactor->rods.size(); i++) - { - sim::reactor::rod* r = sim::system::active.reactor->rods[i].get(); - - if(r->should_select()) - { - r->selected = state; - } - } -} - void core::init() { mesh1.model_matrix = locations::monitors[2]; @@ -80,6 +123,7 @@ void core::init() m_buttons[7].load_model("../assets/model/", "reactor_core_button8.stl"); m_buttons[8].load_model("../assets/model/", "reactor_core_button9.stl"); m_joystick.load_model("../assets/model/", "reactor_core_joystick.stl"); + m_monitor.load_model("../assets/model/", "reactor_core_input.stl"); m_scram.load_model("../assets/model/", "reactor_core_scram.stl"); } @@ -87,28 +131,28 @@ void core::update() { sim::system& sys = sim::system::active; + if(m_monitor.check_focus()) + focus::set(std::make_unique()); if(m_joystick.check_focus()) focus::set(std::make_unique()); if(m_scram.check_focus()) - sim::system::active.reactor->scram(); + sys.reactor->scram(); if(m_buttons[0].check_focus()) set_all(true); if(m_buttons[1].check_focus()) - sys.reactor->move_cursor(-sim::system::active.reactor->height); + sys.reactor->move_cursor(-sys.reactor->height); if(m_buttons[2].check_focus()) set_all(false); if(m_buttons[3].check_focus()) sys.reactor->move_cursor(-1); if(m_buttons[4].check_focus()) - sys.reactor->move_cursor(sim::system::active.reactor->height); + sys.reactor->toggle_selected(); if(m_buttons[5].check_focus()) sys.reactor->move_cursor(1); if(m_buttons[6].check_focus()) - sim::system::active.reactor->toggle_selected(); + sys.reactor->reset_rod_speed(); if(m_buttons[7].check_focus()) - sim::system::active.reactor->reset_rod_speed(); -// if(m_buttons[8].check_focus()) -// + sys.reactor->move_cursor(sys.reactor->height); } void core::render() diff --git a/src/graphics/monitor/core.hpp b/src/graphics/monitor/core.hpp index 71653dd..e6981f1 100644 --- a/src/graphics/monitor/core.hpp +++ b/src/graphics/monitor/core.hpp @@ -2,7 +2,6 @@ #pragma once #include "../mesh/glmesh.hpp" -#include "../../system.hpp" namespace sim::graphics::monitor { @@ -11,6 +10,7 @@ class core { sim::graphics::glmesh mesh1, mesh2; + sim::graphics::mesh m_monitor; sim::graphics::mesh m_buttons[9]; sim::graphics::mesh m_joystick; sim::graphics::mesh m_scram; diff --git a/src/graphics/monitor/helpers.hpp b/src/graphics/monitor/helpers.hpp new file mode 100644 index 0000000..4aae605 --- /dev/null +++ b/src/graphics/monitor/helpers.hpp @@ -0,0 +1,14 @@ + +#pragma once + +#include + +constexpr double show(double v, double m) +{ + return std::round(v * m) / m; +} + +constexpr double show(double v) +{ + return std::round(v * 1e3) * 1e-3; +} diff --git a/src/graphics/monitor/primary_loop.cpp b/src/graphics/monitor/primary_loop.cpp new file mode 100644 index 0000000..bd61b5f --- /dev/null +++ b/src/graphics/monitor/primary_loop.cpp @@ -0,0 +1,113 @@ + +#include +#include + +#include "helpers.hpp" +#include "primary_loop.hpp" +#include "../locations.hpp" +#include "../../system.hpp" +#include "../../coolant/valve.hpp" +#include "../input/focus.hpp" + +#include + +using namespace sim::graphics; +using namespace sim::graphics::monitor; + +struct valve_joystick : public focus::focus_t +{ + sim::coolant::valve* active; + + valve_joystick(sim::coolant::valve* v) : active(v) + { + + } + + virtual ~valve_joystick() + { + active->clear_open_speed(); + } + + virtual void on_cursor_pos(double x, double y) + { + active->add_open_speed(y * 1e-5); + } + + virtual void on_mouse_button(int button, int action, int mods) + { + if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) + { + focus::clear_focus(); + } + } + + virtual bool cursor_is_visible() + { + return false; + } +}; + + +primary_loop::primary_loop() +{ + +} + +void primary_loop::init() +{ + mesh1.model_matrix = locations::monitors[3]; + mesh2.model_matrix = glm::translate(mesh1.model_matrix, glm::vec3(0.5, 0, 0)); + + mesh1.colour_matrix = mesh2.colour_matrix = { + 1, 1, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 + }; + + std::stringstream ss; + sim::graphics::mesh rmesh; + + ss << "Turbine Valves\n\n"; + ss << "Inlet\n"; + ss << "Bypass\n"; + + rmesh.load_text(ss.str().c_str(), 0.04); + mesh1.bind(); + mesh1.set(rmesh, GL_STATIC_DRAW); + + m_joystick_turbine_bypass.load_model("../assets/model", "turbine_valve_bypass_joystick.stl"); + m_joystick_turbine_inlet.load_model("../assets/model", "turbine_valve_inlet_joystick.stl"); +} + +void primary_loop::update() +{ + std::stringstream ss; + sim::graphics::mesh rmesh; + system& sys = sim::system::active; + + ss << "\n\n"; + ss << show( sys.turbine_inlet_valve->get_state() * 100 ) << " %\n"; + ss << show( sys.turbine_bypass_valve->get_state() * 100 ) << " %\n"; + + rmesh.load_text(ss.str().c_str(), 0.04); + mesh2.bind(); + mesh2.set(rmesh, GL_DYNAMIC_DRAW); + + if(m_joystick_turbine_bypass.check_focus()) + focus::set(std::make_unique(sys.turbine_bypass_valve.get())); + if(m_joystick_turbine_inlet.check_focus()) + focus::set(std::make_unique(sys.turbine_inlet_valve.get())); +} + +void primary_loop::render() +{ + mesh1.bind(); + mesh1.uniform(); + mesh1.render(); + + mesh2.bind(); + mesh2.uniform(); + mesh2.render(); +} + diff --git a/src/graphics/monitor/primary_loop.hpp b/src/graphics/monitor/primary_loop.hpp new file mode 100644 index 0000000..5982126 --- /dev/null +++ b/src/graphics/monitor/primary_loop.hpp @@ -0,0 +1,25 @@ + +#pragma once + +#include "../mesh/glmesh.hpp" + +namespace sim::graphics::monitor +{ + +class primary_loop +{ + sim::graphics::glmesh mesh1, mesh2; + + sim::graphics::mesh m_joystick_turbine_bypass; + sim::graphics::mesh m_joystick_turbine_inlet; + +public: + + primary_loop(); + void init(); + void update(); + void render(); +}; + +}; + diff --git a/src/graphics/monitor/vessel.cpp b/src/graphics/monitor/vessel.cpp index 6f133c7..45daa36 100644 --- a/src/graphics/monitor/vessel.cpp +++ b/src/graphics/monitor/vessel.cpp @@ -3,9 +3,11 @@ #include #include "vessel.hpp" +#include "helpers.hpp" #include "../../reactor/rod.hpp" #include "../../reactor/control/boron_rod.hpp" #include "../locations.hpp" +#include "../../system.hpp" #include #include @@ -49,16 +51,6 @@ void vessel::init() mesh1.set(rmesh, GL_STATIC_DRAW); } -static double show(double v, double m) -{ - return std::round(v * m) / m; -} - -static double show(double v) -{ - return std::round(v * 1e3) * 1e-3; -} - void vessel::update() { sim::system& sys = sim::system::active; diff --git a/src/graphics/monitor/vessel.hpp b/src/graphics/monitor/vessel.hpp index fb5e3a4..3906568 100644 --- a/src/graphics/monitor/vessel.hpp +++ b/src/graphics/monitor/vessel.hpp @@ -2,7 +2,6 @@ #pragma once #include "../mesh/glmesh.hpp" -#include "../../system.hpp" namespace sim::graphics::monitor { diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index abbfc1d..dedef36 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -20,6 +20,7 @@ #include "locations.hpp" #include "monitor/vessel.hpp" #include "monitor/core.hpp" +#include "monitor/primary_loop.hpp" #include "mesh/texture.hpp" #include "ui.hpp" @@ -31,6 +32,7 @@ static bool win_should_close = false; static glmesh MeshScene; static monitor::vessel MonitorVessel; static monitor::core MonitorCore; +static monitor::primary_loop MonitorPrimaryLoop; glm::mat4 window::projection_matrix; @@ -113,6 +115,7 @@ void window::create() MonitorCore.init(); MonitorVessel.init(); + MonitorPrimaryLoop.init(); glfwShowWindow(win); glViewport(0, 0, 800, 600); @@ -124,6 +127,7 @@ void window::update(double dt) MonitorCore.update(); MonitorVessel.update(); + MonitorPrimaryLoop.update(); ui::update(dt); } @@ -145,6 +149,7 @@ void window::render() MonitorCore.render(); MonitorVessel.render(); + MonitorPrimaryLoop.render(); ui::render(); diff --git a/src/reactor/builder.cpp b/src/reactor/builder.cpp index f78509f..3f30e58 100644 --- a/src/reactor/builder.cpp +++ b/src/reactor/builder.cpp @@ -7,7 +7,7 @@ using namespace sim::reactor; -sim::reactor::reactor sim::reactor::builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, coolant::vessel& v, const char** lines) +sim::reactor::reactor sim::reactor::builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, coolant::vessel* v, const char** lines) { std::vector> arr(W * H); diff --git a/src/reactor/builder.hpp b/src/reactor/builder.hpp index ecb1a5f..1064be0 100644 --- a/src/reactor/builder.hpp +++ b/src/reactor/builder.hpp @@ -13,7 +13,7 @@ namespace sim::reactor { -reactor builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, coolant::vessel& v, const char** lines); +reactor builder(const int W, const int H, const double CW, const double CH, fuel::fuel_rod fr, coolant::vessel* v, const char** lines); }; diff --git a/src/reactor/control/boron_rod.cpp b/src/reactor/control/boron_rod.cpp index f7196ad..1830fc8 100644 --- a/src/reactor/control/boron_rod.cpp +++ b/src/reactor/control/boron_rod.cpp @@ -9,7 +9,7 @@ constexpr double boron_density = 2340000; // g/m^3 constexpr double boron_molar_mass = 10; // g/mol constexpr double boron_molar_density = boron_density / boron_molar_mass; // mol/m^3 -boron_rod::boron_rod(coolant::vessel& v) : coolant::pipe(v) +boron_rod::boron_rod(coolant::vessel* v) : coolant::pipe(v) { } diff --git a/src/reactor/control/boron_rod.hpp b/src/reactor/control/boron_rod.hpp index 63ef41a..7652cf0 100644 --- a/src/reactor/control/boron_rod.hpp +++ b/src/reactor/control/boron_rod.hpp @@ -18,7 +18,7 @@ class boron_rod : public coolant::pipe public: - boron_rod(coolant::vessel& v); + boron_rod(coolant::vessel* v); virtual void update(double secs); void set_reactivity(double a); diff --git a/src/reactor/coolant/pipe.cpp b/src/reactor/coolant/pipe.cpp index 984d229..bc06964 100644 --- a/src/reactor/coolant/pipe.cpp +++ b/src/reactor/coolant/pipe.cpp @@ -4,9 +4,9 @@ using namespace sim::reactor::coolant; -pipe::pipe(coolant::vessel& v) +pipe::pipe(coolant::vessel* v) { - this->vessel = &v; + this->vessel = v; this->steam = 0; } diff --git a/src/reactor/coolant/pipe.hpp b/src/reactor/coolant/pipe.hpp index 71cd9c1..001e9c1 100644 --- a/src/reactor/coolant/pipe.hpp +++ b/src/reactor/coolant/pipe.hpp @@ -22,7 +22,7 @@ protected: public: - pipe(coolant::vessel& v); + pipe(coolant::vessel* v); virtual std::unique_ptr clone() const { return std::make_unique(*this); } virtual bool should_display() const { return true; } diff --git a/src/system.cpp b/src/system.cpp index 8ee6dac..e3c787d 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -36,11 +36,13 @@ system::system() }; vessel = std::make_unique(sim::coolant::WATER, 8, 10, 300); - reactor = std::make_unique(sim::reactor::builder(19, 19, 1.0 / 4.0, 4, reactor::fuel::fuel_rod(0.5), *vessel.get(), layout)); + reactor = std::make_unique(sim::reactor::builder(19, 19, 1.0 / 4.0, 4, reactor::fuel::fuel_rod(0.5), vessel.get(), layout)); condenser = std::make_unique(sim::coolant::WATER, 8, 6, 200); + turbine = std::make_unique(sim::coolant::WATER, 6, 3, 20); - turbine_bypass_valve = std::make_unique(*vessel.get(), *condenser.get(), 1); - core_pump = std::make_unique(*condenser.get(), *vessel.get(), 1e6, 1, 100, 100); + turbine_inlet_valve = std::make_unique(vessel.get(), turbine.get(), 1); + turbine_bypass_valve = std::make_unique(vessel.get(), condenser.get(), 1); + core_pump = std::make_unique(condenser.get(), vessel.get(), 1e6, 1, 100, 100); } system::system(system&& o) @@ -48,7 +50,9 @@ system::system(system&& o) vessel = std::move(o.vessel); reactor = std::move(o.reactor); condenser = std::move(o.condenser); + turbine = std::move(o.turbine); turbine_bypass_valve = std::move(o.turbine_bypass_valve); + turbine_inlet_valve = std::move(o.turbine_inlet_valve); core_pump = std::move(o.core_pump); } @@ -57,8 +61,10 @@ void system::update(double dt) dt *= speed; vessel->update(dt); reactor->update(dt); -// condenser->update(dt); -// turbine_bypass_valve->update(dt); + condenser->update(dt); + turbine->update(dt); + turbine_inlet_valve->update(dt); + turbine_bypass_valve->update(dt); // core_pump->update(dt); } diff --git a/src/system.hpp b/src/system.hpp index db4405d..f3cfae0 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -8,6 +8,7 @@ #include "coolant/pump.hpp" #include "coolant/valve.hpp" #include "coolant/condenser.hpp" +#include "electric/turbine.hpp" #include "graphics/mesh/mesh.hpp" namespace sim @@ -20,8 +21,10 @@ struct system std::unique_ptr reactor; std::unique_ptr vessel; std::unique_ptr condenser; + std::unique_ptr turbine; std::unique_ptr core_pump; std::unique_ptr turbine_bypass_valve; + std::unique_ptr turbine_inlet_valve; sim::graphics::mesh scene; double speed = 1; From daef33a303cd796a00576c4b26a9a7e6c9c2cbad Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sat, 3 Feb 2024 23:35:59 +1100 Subject: [PATCH 40/43] fix for floating point issues --- src/coolant/condenser.cpp | 5 ----- src/coolant/condenser.hpp | 2 -- src/coolant/fluid_holder.cpp | 7 ++++-- src/coolant/fluid_holder.hpp | 5 ----- src/electric/turbine.cpp | 5 ----- src/electric/turbine.hpp | 2 -- src/graphics/mesh/mesh.cpp | 16 +++++++++++++- src/main.cpp | 3 +++ src/reactor/coolant/vessel.cpp | 40 +++++++++++++++++++++++++++++++++- src/reactor/coolant/vessel.hpp | 6 ++++- src/reactor/reactor.hpp | 5 +++-- src/reactor/rod.cpp | 1 + src/system.cpp | 2 +- 13 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/coolant/condenser.cpp b/src/coolant/condenser.cpp index f3b6472..bae199b 100644 --- a/src/coolant/condenser.cpp +++ b/src/coolant/condenser.cpp @@ -19,11 +19,6 @@ condenser::condenser(fluid_t type, double height, double diameter, double level) this->level = level; } -double condenser::get_bubble_hl() -{ - return (level / volume) * diameter * 0.5 / fluid.bubble_speed; -} - void condenser::update(double secs) { ((sim::coolant::fluid_holder*)this)->update(secs); diff --git a/src/coolant/condenser.hpp b/src/coolant/condenser.hpp index cbd8414..1b8c795 100644 --- a/src/coolant/condenser.hpp +++ b/src/coolant/condenser.hpp @@ -10,8 +10,6 @@ class condenser : public fluid_holder { const double height; const double diameter; - - virtual double get_bubble_hl(); public: diff --git a/src/coolant/fluid_holder.cpp b/src/coolant/fluid_holder.cpp index 9682663..266a382 100644 --- a/src/coolant/fluid_holder.cpp +++ b/src/coolant/fluid_holder.cpp @@ -5,6 +5,7 @@ #include "../reactor/fuel/half_life.hpp" #include +#include using namespace sim::coolant; @@ -19,8 +20,12 @@ double fluid_holder::add_heat(double m1, double t1) double t = t1 - t2; double m2 = (fluid.l_to_g(level) + steam) * fluid.jPgk; double m = m1 + m2; + + if(m1 == 0 || m2 == 0) + return t1; heat = t1 - t * m2 / m; + return heat; } @@ -124,7 +129,5 @@ void fluid_holder::update(double secs) level = fluid.g_to_l(l); heat -= diff * fluid.jPg / (fluid.l_to_g(level) + steam) / fluid.jPgk; - if(diff > 0) steam_suspended += diff; - steam_suspended *= reactor::fuel::half_life::get(secs, get_bubble_hl()); } diff --git a/src/coolant/fluid_holder.hpp b/src/coolant/fluid_holder.hpp index 68bcd2e..f191344 100644 --- a/src/coolant/fluid_holder.hpp +++ b/src/coolant/fluid_holder.hpp @@ -15,7 +15,6 @@ protected: double level = 0; // litres double steam = 0; // grams - double steam_suspended = 0; // grams double heat = 0; // celsius fluid_holder(fluid_t fluid, double volume); @@ -33,10 +32,6 @@ public: constexpr double get_steam() const { return steam; } // grams constexpr double get_mass() const { return fluid.l_to_g(level) + steam; } // grams constexpr double get_steam_density() const { return steam / (volume - level); } // g/L - constexpr double get_steam_suspended() const { return steam_suspended; } // grams - constexpr double get_void_ratio() const { double s = steam_suspended / get_steam_density(); return s / (level + s); } - - virtual double get_bubble_hl() = 0; double get_pressure() const; // pascals void update(double dt); diff --git a/src/electric/turbine.cpp b/src/electric/turbine.cpp index 286127a..9f77877 100644 --- a/src/electric/turbine.cpp +++ b/src/electric/turbine.cpp @@ -19,11 +19,6 @@ turbine::turbine(coolant::fluid_t type, double height, double diameter, double l this->level = level; } -double turbine::get_bubble_hl() -{ - return (level / volume) * diameter * 0.5 / fluid.bubble_speed; -} - void turbine::update(double secs) { ((sim::coolant::fluid_holder*)this)->update(secs); diff --git a/src/electric/turbine.hpp b/src/electric/turbine.hpp index aa9afc5..035c7d9 100644 --- a/src/electric/turbine.hpp +++ b/src/electric/turbine.hpp @@ -11,8 +11,6 @@ class turbine : public sim::coolant::fluid_holder const double height; const double diameter; - virtual double get_bubble_hl(); - public: turbine(coolant::fluid_t type, double height, double diameter, double level); diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp index bf9644e..01c9b39 100644 --- a/src/graphics/mesh/mesh.cpp +++ b/src/graphics/mesh/mesh.cpp @@ -178,7 +178,11 @@ static bool calc_intercept_vert(vec3 v[3], vec3 pos, vec3& path, vec3& path_n, v path -= normal * d; normal_last = normal; l = glm::length(path); - path_n = path / l; + + if(l > 0) + { + path_n = path / l; + } return true; } @@ -207,6 +211,11 @@ vec3 mesh::calc_intersect(vec3 pos, vec3 path, vec3& normal_last) const { i_found = i; } + + if(l == 0) + { + return path; + } } for(unsigned int i = 0; i < i_found; i += 3) @@ -218,6 +227,11 @@ vec3 mesh::calc_intersect(vec3 pos, vec3 path, vec3& normal_last) const }; calc_intercept_vert(v, pos, path, path_n, normal_last, l); + + if(l == 0) + { + return path; + } } return path; diff --git a/src/main.cpp b/src/main.cpp index d07598c..870a083 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "reactor/coolant/vessel.hpp" #include "coolant/fluid_t.hpp" @@ -29,6 +30,8 @@ unsigned long get_now() int main() { + feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); + graphics::window::create(); long clock = get_now(); diff --git a/src/reactor/coolant/vessel.cpp b/src/reactor/coolant/vessel.cpp index b594669..3b4f538 100644 --- a/src/reactor/coolant/vessel.cpp +++ b/src/reactor/coolant/vessel.cpp @@ -4,6 +4,7 @@ #include "../../conversions/temperature.hpp" #include "../fuel/half_life.hpp" +#include #include using namespace sim::reactor::coolant; @@ -22,13 +23,50 @@ vessel::vessel(sim::coolant::fluid_t fluid, double height, double diameter, doub this->level = level; } -double vessel::get_bubble_hl() +double vessel::get_steam_suspended() const +{ + return steam_suspended; +} + +double vessel::get_void_ratio() const +{ + double s = steam_suspended / get_steam_density(); + + if(s == 0) + { + return 0; + } + + return s / (level + s); +} + +double vessel::get_bubble_hl() const { return (level / volume) * height * 0.5 / fluid.bubble_speed; } void vessel::update(double secs) { + double s = steam; + ((sim::coolant::fluid_holder*)this)->update(secs); + + double diff = steam - s; + double hl = get_bubble_hl(); + + if(diff > 0) + { + steam_suspended += diff; + } + + if(hl > 0) + { + steam_suspended *= reactor::fuel::half_life::get(secs, get_bubble_hl()); + } + + else + { + steam_suspended = 0; + } } diff --git a/src/reactor/coolant/vessel.hpp b/src/reactor/coolant/vessel.hpp index 4533075..1b10163 100644 --- a/src/reactor/coolant/vessel.hpp +++ b/src/reactor/coolant/vessel.hpp @@ -14,10 +14,14 @@ public: const double height; // meters const double diameter; // meters + double steam_suspended = 0; // grams vessel(sim::coolant::fluid_t fluid, double height, double diameter, double level); - virtual double get_bubble_hl(); + double get_steam_suspended() const; // grams + double get_void_ratio() const; + double get_bubble_hl() const; + void update(double secs); friend std::ostream& operator<<(std::ostream& o, const vessel& v) diff --git a/src/reactor/reactor.hpp b/src/reactor/reactor.hpp index 05c1925..f3d80e6 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -14,10 +14,11 @@ struct reactor { const double cell_width; const double cell_height; + const int width; const int height; const int size; - + std::vector> rods; double rod_speed = 0; int cursor; @@ -38,7 +39,7 @@ struct reactor double get_total(rod::val_t type); int move_cursor(int d); void toggle_selected(); - + private: void update_tile(double secs, int i, int x, int y); diff --git a/src/reactor/rod.cpp b/src/reactor/rod.cpp index cc30173..d0379eb 100644 --- a/src/reactor/rod.cpp +++ b/src/reactor/rod.cpp @@ -31,6 +31,7 @@ double rod::extract(val_t type, double s, double k, double o) m = 1 - std::pow(0.5, s * -std::log2(k)); } + double v = m * 0.5 * (get(type) - o); vals[type] -= v; diff --git a/src/system.cpp b/src/system.cpp index e3c787d..6ffa934 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -37,7 +37,7 @@ system::system() vessel = std::make_unique(sim::coolant::WATER, 8, 10, 300); reactor = std::make_unique(sim::reactor::builder(19, 19, 1.0 / 4.0, 4, reactor::fuel::fuel_rod(0.5), vessel.get(), layout)); - condenser = std::make_unique(sim::coolant::WATER, 8, 6, 200); + condenser = std::make_unique(sim::coolant::WATER, 8, 6, 20); turbine = std::make_unique(sim::coolant::WATER, 6, 3, 20); turbine_inlet_valve = std::make_unique(vessel.get(), turbine.get(), 1); From a9cb24c28c30ff6c03c961f23f08fdf8168e7a0c Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Sun, 4 Feb 2024 23:22:15 +1100 Subject: [PATCH 41/43] steam flow sort of works --- CMakeLists.txt | 2 +- src/coolant/fluid_holder.cpp | 85 ++++++++++++--------------- src/coolant/fluid_holder.hpp | 5 +- src/coolant/valve.cpp | 37 +++++++++++- src/coolant/valve.hpp | 4 +- src/electric/turbine.cpp | 18 ++++++ src/graphics/input/focus.cpp | 29 ++++++--- src/graphics/input/focus.hpp | 4 +- src/graphics/input/keyboard.cpp | 2 +- src/graphics/monitor/core.cpp | 9 ++- src/graphics/monitor/helpers.hpp | 5 +- src/graphics/monitor/primary_loop.cpp | 35 +++++++++-- src/main.cpp | 2 +- src/reactor/coolant/vessel.cpp | 16 +++-- src/reactor/rod.cpp | 1 - src/system.cpp | 8 +-- 16 files changed, 178 insertions(+), 84 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f0422a..b09541e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25) project(FastNuclearSim VERSION 1.0) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 26) set(CMAKE_CXX_FLAGS "-g -O3 -I/usr/include/freetype2") file(GLOB_RECURSE SOURCES src/*.cpp) diff --git a/src/coolant/fluid_holder.cpp b/src/coolant/fluid_holder.cpp index 266a382..7de0e9c 100644 --- a/src/coolant/fluid_holder.cpp +++ b/src/coolant/fluid_holder.cpp @@ -18,7 +18,7 @@ double fluid_holder::add_heat(double m1, double t1) { double t2 = get_heat(); double t = t1 - t2; - double m2 = (fluid.l_to_g(level) + steam) * fluid.jPgk; + double m2 = get_mass() * fluid.jPgk; double m = m1 + m2; if(m1 == 0 || m2 == 0) @@ -62,33 +62,24 @@ double fluid_holder::extract_fluid(double amount) return amount; } -double fluid_holder::extract_steam(double dt, double a, double p2) +void fluid_holder::add_steam(double m2, double t2) { - // calculate the mass moved - double p1 = get_pressure(); - double p = (p1 - p2) * 0.001; // mPa or g/m/s^2 + double m1 = steam; + double t1 = heat; + double m = m1 + m2; - if(p == 0) + if(m > 0) { - return 0; + heat = (t1 * m1 + t2 * m2) / m; } - double V = (volume - level) * 0.001; // m^3 - double mass = std::min(dt * a * p / std::sqrt( V * std::abs(p) / steam ), steam); - - if(std::isnan(mass)) - { - return 0; - } - - steam -= mass; - return mass; + steam = m; } double fluid_holder::get_pressure() const { double T = conversions::temperature::c_to_k(heat); - double V = (volume - level) * 0.001; + double V = get_steam_volume() * 0.001; double n = fluid.g_to_mol(steam); return (n * T * constants::R) / V; @@ -96,38 +87,36 @@ double fluid_holder::get_pressure() const void fluid_holder::update(double secs) { - double V = (volume - level) * 0.001; - double P = fluid.vapor_pressure.calc_p(heat); - double T = conversions::temperature::c_to_k(heat); - double n = fluid.mol_to_g((V * P) / (T * constants::R)) - steam; + double mass = get_mass(); - double s = steam + n; - double l = fluid.l_to_g(level) - n; - double v = fluid.l_to_g(volume); - - if(l < 0) + if(mass > 0) { - s += l; - l = 0; + double V = get_steam_volume() * 0.001; + double P = fluid.vapor_pressure.calc_p(heat); + double T = conversions::temperature::c_to_k(heat); + double n = fluid.mol_to_g((V * P) / (T * constants::R)) - steam; + + double s = steam + n; + double l = fluid.l_to_g(level) - n; + double v = fluid.l_to_g(volume); + + if(l < 0) + { + s += l; + l = 0; + } + + if(l > v) + { + l = v; + s = 0; + } + + double diff = s - steam; + + steam = s; + level = fluid.g_to_l(l); + heat -= diff * fluid.jPg / mass / fluid.jPgk; } - - if(s > v) - { - s = v; - l = 0; - } - - if(l > v) - { - l = v; - s = 0; - } - - double diff = s - steam; - - steam = s; - level = fluid.g_to_l(l); - heat -= diff * fluid.jPg / (fluid.l_to_g(level) + steam) / fluid.jPgk; - } diff --git a/src/coolant/fluid_holder.hpp b/src/coolant/fluid_holder.hpp index f191344..9374f5d 100644 --- a/src/coolant/fluid_holder.hpp +++ b/src/coolant/fluid_holder.hpp @@ -23,15 +23,16 @@ public: double add_heat(double m, double t); double add_fluid(double amount, double heat); - double extract_steam(double dt, double a, double p2); double extract_fluid(double amount); + virtual void add_steam(double amount, double t); constexpr double get_volume() const { return volume; } // litres constexpr double get_level() const { return level; } // litres constexpr double get_heat() const { return heat; } // celsius constexpr double get_steam() const { return steam; } // grams + constexpr double get_steam_volume() const { return volume - level; } // litres + constexpr double get_steam_density() const { return steam / get_steam_volume(); } // g/L constexpr double get_mass() const { return fluid.l_to_g(level) + steam; } // grams - constexpr double get_steam_density() const { return steam / (volume - level); } // g/L double get_pressure() const; // pascals void update(double dt); diff --git a/src/coolant/valve.cpp b/src/coolant/valve.cpp index 79ed2e2..4d28159 100644 --- a/src/coolant/valve.cpp +++ b/src/coolant/valve.cpp @@ -1,6 +1,9 @@ #include "valve.hpp" +#include +#include + using namespace sim::coolant; valve::valve(fluid_holder* src, fluid_holder* dst, double max) : src(src), dst(dst), max(max) @@ -10,18 +13,46 @@ valve::valve(fluid_holder* src, fluid_holder* dst, double max) : src(src), dst(d void valve::add_open_speed(double v) { - open_speed += v; + speed += v; } void valve::clear_open_speed() { - open_speed = 0; + speed = 0; } void valve::update(double dt) { - state += open_speed * dt; + state += speed * dt; + if(state > 1) state = 1; if(state < 0) state = 0; + + double r = max * state; // L + double m = r * dt;//1 - std::pow(0.5, dt * -std::log2(1 - r)); + + double pressure1 = src->get_pressure(); + double pressure2 = dst->get_pressure(); + double density1 = src->get_steam_density(); // g/L + double density2 = dst->get_steam_density(); // g/L + double diff = (pressure1 - pressure2) * m; + double temp, mass; + + if(diff > 0) + { + temp = src->get_heat(); + mass = diff * src->get_steam_density(); + } + + else + { + temp = dst->get_heat(); + mass = diff * dst->get_steam_density(); + } + + src->add_steam(-mass, temp); + dst->add_steam(mass, temp); + + this->flow = mass / dt; } diff --git a/src/coolant/valve.hpp b/src/coolant/valve.hpp index 45060f2..4b1cd9f 100644 --- a/src/coolant/valve.hpp +++ b/src/coolant/valve.hpp @@ -13,8 +13,9 @@ class valve fluid_holder* const src; fluid_holder* const dst; - double open_speed = 0; + double speed = 0; double state = 0; + double flow = 0; // L/s public: @@ -25,6 +26,7 @@ public: void clear_open_speed(); constexpr double get_state() const { return state; } + constexpr double get_flow() const { return flow; } }; }; diff --git a/src/electric/turbine.cpp b/src/electric/turbine.cpp index 9f77877..8f20159 100644 --- a/src/electric/turbine.cpp +++ b/src/electric/turbine.cpp @@ -1,5 +1,6 @@ #include "turbine.hpp" +#include "../system.hpp" #include #include @@ -21,7 +22,24 @@ turbine::turbine(coolant::fluid_t type, double height, double diameter, double l void turbine::update(double secs) { + sim::system& sys = sim::system::active; + auto& o = *sys.turbine.get(); + ((sim::coolant::fluid_holder*)this)->update(secs); + +/* if(level + o.level > o.volume) + { + level += o.level - o.volume; + o.level = o.volume; + } + + else + { + o.level += level; + level = 0; + } + + balance_steam(o);*/ } diff --git a/src/graphics/input/focus.cpp b/src/graphics/input/focus.cpp index d7674a2..459a6ca 100644 --- a/src/graphics/input/focus.cpp +++ b/src/graphics/input/focus.cpp @@ -12,12 +12,14 @@ #include #include +#include using namespace sim::graphics; static glm::vec<3, double> trigger_near; static glm::vec<3, double> trigger_far; +static std::vector> stack; static std::unique_ptr state = nullptr; static bool mouse_visible = false; static bool mouse_locked = false; @@ -51,11 +53,9 @@ void focus::on_mouse_button(int button, int action, int mods) state->on_mouse_button(button, action, mods); } - else if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) + if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) { - triggered = true; - - if(is_mouse_locked()) + if(is_mouse_locked() && mouse_visible) { double mx, my; mouse::get(mx, my); @@ -66,12 +66,14 @@ void focus::on_mouse_button(int button, int action, int mods) trigger_near = glm::unProject(glm::vec3(mouse, -1), camera::get_matrix(), window::projection_matrix, viewport); trigger_far = glm::unProject(glm::vec3(mouse, 1), camera::get_matrix(), window::projection_matrix, viewport); + triggered = true; } - else + else if(!mouse_visible) { trigger_near = camera::get_pos(); trigger_far = trigger_near + camera::get_normal(); + triggered = true; } } } @@ -102,7 +104,7 @@ glm::vec<3, double> focus::get_trigger_far() return trigger_far; } -void focus::update() +void focus::update(double dt) { triggered = false; @@ -130,7 +132,7 @@ void focus::update() if(state) { - state->update(); + state->update(dt); } } @@ -142,6 +144,12 @@ bool focus::is_focused() void focus::clear_focus() { state = nullptr; + + if(stack.size() != 0) + { + state = std::move(stack.back()); + stack.pop_back(); + } } bool focus::is_mouse_locked() @@ -152,11 +160,16 @@ bool focus::is_mouse_locked() void focus::clear_mouse_locked() { mouse_locked = false; - state = nullptr; + clear_focus(); } void focus::set(std::unique_ptr f) { + if(state != nullptr) + { + stack.push_back(std::move(state)); + } + state = std::move(f); } diff --git a/src/graphics/input/focus.hpp b/src/graphics/input/focus.hpp index a586f0f..ad8f5cb 100644 --- a/src/graphics/input/focus.hpp +++ b/src/graphics/input/focus.hpp @@ -16,7 +16,7 @@ struct focus_t virtual void on_mouse_button(int button, int action, int mods) { } virtual void on_cursor_pos(double x, double y) { } virtual void on_charcode(unsigned int c) { } - virtual void update() { } + virtual void update(double dt) { } }; bool is_focused(); @@ -31,7 +31,7 @@ void on_keypress(int key, int sc, int action, int mods); void on_mouse_button(int button, int action, int mods); void on_cursor_pos(double x, double y); void on_charcode(unsigned int c); -void update(); +void update(double dt); }; diff --git a/src/graphics/input/keyboard.cpp b/src/graphics/input/keyboard.cpp index 9145ba6..391999b 100644 --- a/src/graphics/input/keyboard.cpp +++ b/src/graphics/input/keyboard.cpp @@ -61,7 +61,7 @@ static void cb_charcode(GLFWwindow* win, unsigned int code) bool keyboard::is_pressed(int key) { - if(focus::is_focused()) + if(focus::is_mouse_locked()) { return false; } diff --git a/src/graphics/monitor/core.cpp b/src/graphics/monitor/core.cpp index 0604e25..12054b1 100644 --- a/src/graphics/monitor/core.cpp +++ b/src/graphics/monitor/core.cpp @@ -73,9 +73,16 @@ struct core_monitor : public focus::focus_t struct core_joystick : public focus::focus_t { + double ypos = 0; + virtual void on_cursor_pos(double x, double y) { - sim::system::active.reactor->add_rod_speed(y * 1e-6); + ypos += y; + } + + virtual void update(double dt) + { + sim::system::active.reactor->add_rod_speed(ypos * dt * 1e-6); } virtual void on_mouse_button(int button, int action, int mods) diff --git a/src/graphics/monitor/helpers.hpp b/src/graphics/monitor/helpers.hpp index 4aae605..4e64441 100644 --- a/src/graphics/monitor/helpers.hpp +++ b/src/graphics/monitor/helpers.hpp @@ -8,7 +8,8 @@ constexpr double show(double v, double m) return std::round(v * m) / m; } -constexpr double show(double v) +constexpr double show(double a) { - return std::round(v * 1e3) * 1e-3; + double b = std::round(a * 1e3) * 1e-3; + return b == 0 ? 0 : b; } diff --git a/src/graphics/monitor/primary_loop.cpp b/src/graphics/monitor/primary_loop.cpp index bd61b5f..7720471 100644 --- a/src/graphics/monitor/primary_loop.cpp +++ b/src/graphics/monitor/primary_loop.cpp @@ -10,6 +10,7 @@ #include "../input/focus.hpp" #include +#include using namespace sim::graphics; using namespace sim::graphics::monitor; @@ -30,7 +31,7 @@ struct valve_joystick : public focus::focus_t virtual void on_cursor_pos(double x, double y) { - active->add_open_speed(y * 1e-5); + active->add_open_speed(-y * 1e-6); } virtual void on_mouse_button(int button, int action, int mods) @@ -68,9 +69,20 @@ void primary_loop::init() std::stringstream ss; sim::graphics::mesh rmesh; - ss << "Turbine Valves\n\n"; - ss << "Inlet\n"; - ss << "Bypass\n"; + ss << "Turbine Bypass Valve\n\n"; + ss << "Opened\nFlow\n\n"; + ss << "Turbine Inlet Valve\n\n"; + ss << "Opened\nFlow\n\n"; + ss << "Turbine\n\n"; + ss << "Heat\n"; + ss << "Steam\n"; + ss << "Pressure\n"; + ss << "Level\n\n"; + ss << "Condenser\n\n"; + ss << "Heat\n"; + ss << "Steam\n"; + ss << "Pressure\n"; + ss << "Level\n\n"; rmesh.load_text(ss.str().c_str(), 0.04); mesh1.bind(); @@ -87,8 +99,21 @@ void primary_loop::update() system& sys = sim::system::active; ss << "\n\n"; - ss << show( sys.turbine_inlet_valve->get_state() * 100 ) << " %\n"; ss << show( sys.turbine_bypass_valve->get_state() * 100 ) << " %\n"; + ss << show( sys.turbine_bypass_valve->get_flow() ) << " g/s\n"; + ss << "\n\n\n"; + ss << show( sys.turbine_inlet_valve->get_state() * 100 ) << " %\n"; + ss << show( sys.turbine_inlet_valve->get_flow() ) << " g/s\n"; + ss << "\n\n\n"; + ss << show( sys.turbine->get_heat() ) << " C\n"; + ss << show( sys.turbine->get_steam() ) << " g\n"; + ss << show( sys.turbine->get_pressure() / 1000 ) << " kPa\n"; + ss << sys.turbine->get_level() << " / " << show( sys.turbine->get_volume() ) << " L\n"; + ss << "\n\n\n"; + ss << show( sys.condenser->get_heat() ) << " C\n"; + ss << show( sys.condenser->get_steam() ) << " g\n"; + ss << show( sys.condenser->get_pressure() / 1000 ) << " kPa\n"; + ss << sys.condenser->get_level() << " / " << show( sys.condenser->get_volume() ) << " L\n"; rmesh.load_text(ss.str().c_str(), 0.04); mesh2.bind(); diff --git a/src/main.cpp b/src/main.cpp index 870a083..3b316f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,7 +47,7 @@ int main() graphics::camera::update(dt); graphics::window::update(dt); - graphics::focus::update(); + graphics::focus::update(dt); graphics::window::render(); } diff --git a/src/reactor/coolant/vessel.cpp b/src/reactor/coolant/vessel.cpp index 3b4f538..ecd75d9 100644 --- a/src/reactor/coolant/vessel.cpp +++ b/src/reactor/coolant/vessel.cpp @@ -30,14 +30,22 @@ double vessel::get_steam_suspended() const double vessel::get_void_ratio() const { - double s = steam_suspended / get_steam_density(); - - if(s == 0) + double density = get_steam_density(); + + if(density == 0) { return 0; } - return s / (level + s); + double s = steam_suspended / density; + double m = level + s; + + if(m == 0) + { + return 0; + } + + return s / m; } double vessel::get_bubble_hl() const diff --git a/src/reactor/rod.cpp b/src/reactor/rod.cpp index d0379eb..cc30173 100644 --- a/src/reactor/rod.cpp +++ b/src/reactor/rod.cpp @@ -31,7 +31,6 @@ double rod::extract(val_t type, double s, double k, double o) m = 1 - std::pow(0.5, s * -std::log2(k)); } - double v = m * 0.5 * (get(type) - o); vals[type] -= v; diff --git a/src/system.cpp b/src/system.cpp index 6ffa934..909fc30 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -37,11 +37,11 @@ system::system() vessel = std::make_unique(sim::coolant::WATER, 8, 10, 300); reactor = std::make_unique(sim::reactor::builder(19, 19, 1.0 / 4.0, 4, reactor::fuel::fuel_rod(0.5), vessel.get(), layout)); - condenser = std::make_unique(sim::coolant::WATER, 8, 6, 20); - turbine = std::make_unique(sim::coolant::WATER, 6, 3, 20); + condenser = std::make_unique(sim::coolant::WATER, 8, 6, 0); + turbine = std::make_unique(sim::coolant::WATER, 6, 3, 0); - turbine_inlet_valve = std::make_unique(vessel.get(), turbine.get(), 1); - turbine_bypass_valve = std::make_unique(vessel.get(), condenser.get(), 1); + turbine_inlet_valve = std::make_unique(vessel.get(), turbine.get(), 1e-7); + turbine_bypass_valve = std::make_unique(vessel.get(), condenser.get(), 1e-7); core_pump = std::make_unique(condenser.get(), vessel.get(), 1e6, 1, 100, 100); } From 857cd527aec0ef2a9dd2f667d8ca6d63ba3848e3 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Mon, 5 Feb 2024 18:33:31 +1100 Subject: [PATCH 42/43] added some things but its a buggy mess --- src/coolant/condenser.cpp | 4 +- src/coolant/condenser.hpp | 2 +- src/coolant/fluid_holder.cpp | 37 ++++++++++----- src/coolant/fluid_holder.hpp | 35 ++++++++------- src/coolant/fluid_t.hpp | 8 ++-- src/coolant/pump.cpp | 35 ++++++++++++--- src/coolant/pump.hpp | 7 ++- src/coolant/valve.cpp | 65 +++++++++++++++++++-------- src/coolant/valve.hpp | 2 +- src/electric/turbine.cpp | 27 ++++------- src/electric/turbine.hpp | 22 ++++++++- src/graphics/monitor/primary_loop.cpp | 20 ++++----- src/reactor/coolant/vessel.cpp | 4 +- src/reactor/coolant/vessel.hpp | 2 +- src/system.cpp | 25 ++++++----- src/system.hpp | 2 +- 16 files changed, 186 insertions(+), 111 deletions(-) diff --git a/src/coolant/condenser.cpp b/src/coolant/condenser.cpp index bae199b..d77aae7 100644 --- a/src/coolant/condenser.cpp +++ b/src/coolant/condenser.cpp @@ -13,8 +13,8 @@ constexpr static double calc_cylinder(double h, double d) return M_PI * r * r * h; } -condenser::condenser(fluid_t type, double height, double diameter, double level) : - height(height), diameter(diameter), fluid_holder(type, calc_cylinder(height, diameter)) +condenser::condenser(fluid_t type, double height, double diameter, double mass, double level) : + height(height), diameter(diameter), fluid_holder(type, calc_cylinder(height, diameter), mass) { this->level = level; } diff --git a/src/coolant/condenser.hpp b/src/coolant/condenser.hpp index 1b8c795..4268ea1 100644 --- a/src/coolant/condenser.hpp +++ b/src/coolant/condenser.hpp @@ -13,7 +13,7 @@ class condenser : public fluid_holder public: - condenser(fluid_t type, double height, double diameter, double level); + condenser(fluid_t type, double height, double diameter, double mass, double level); void update(double dt); }; diff --git a/src/coolant/fluid_holder.cpp b/src/coolant/fluid_holder.cpp index 7de0e9c..145aab1 100644 --- a/src/coolant/fluid_holder.cpp +++ b/src/coolant/fluid_holder.cpp @@ -9,7 +9,7 @@ using namespace sim::coolant; -fluid_holder::fluid_holder(fluid_t fluid, double volume) : fluid(fluid), volume(volume) +fluid_holder::fluid_holder(fluid_t fluid, double volume, double extra_mass) : fluid(fluid), volume(volume), extra_mass(extra_mass) { } @@ -18,7 +18,7 @@ double fluid_holder::add_heat(double m1, double t1) { double t2 = get_heat(); double t = t1 - t2; - double m2 = get_mass() * fluid.jPgk; + double m2 = get_thermal_mass(); double m = m1 + m2; if(m1 == 0 || m2 == 0) @@ -31,16 +31,18 @@ double fluid_holder::add_heat(double m1, double t1) double fluid_holder::add_fluid(double v2, double t2) { - double v1 = level; - double t1 = get_heat(); - double t = t1 - t2; - if(level + v2 > volume) { v2 = volume - level; } - heat = t1 - t * v2 / (v1 + v2); + int m1 = get_thermal_mass(); + int m2 = fluid.l_to_g(v2); + + double t1 = get_heat(); + double t = t1 - t2; + + heat = t1 - t * m2 / (m1 + m2); level += v2; return v2; @@ -64,16 +66,16 @@ double fluid_holder::extract_fluid(double amount) void fluid_holder::add_steam(double m2, double t2) { - double m1 = steam; + double m1 = get_thermal_mass(); double t1 = heat; double m = m1 + m2; if(m > 0) { - heat = (t1 * m1 + t2 * m2) / m; + heat = t1 - (t1 - t2) * m2 / (m1 + m2); } - steam = m; + steam += m2; } double fluid_holder::get_pressure() const @@ -81,13 +83,24 @@ double fluid_holder::get_pressure() const double T = conversions::temperature::c_to_k(heat); double V = get_steam_volume() * 0.001; double n = fluid.g_to_mol(steam); + + if(V == 0) + { + return NAN; + } return (n * T * constants::R) / V; } +double fluid_holder::get_steam_density() const +{ + double v = get_steam_volume(); + return v > 0 ? steam / v : NAN; +} + void fluid_holder::update(double secs) { - double mass = get_mass(); + double mass = get_thermal_mass(); if(mass > 0) { @@ -116,7 +129,7 @@ void fluid_holder::update(double secs) steam = s; level = fluid.g_to_l(l); - heat -= diff * fluid.jPg / mass / fluid.jPgk; + heat -= diff * fluid.jPg / mass; } } diff --git a/src/coolant/fluid_holder.hpp b/src/coolant/fluid_holder.hpp index 9374f5d..8272f5c 100644 --- a/src/coolant/fluid_holder.hpp +++ b/src/coolant/fluid_holder.hpp @@ -10,31 +10,34 @@ class fluid_holder { protected: - const fluid_t fluid; - const double volume; // litres - double level = 0; // litres double steam = 0; // grams double heat = 0; // celsius - - fluid_holder(fluid_t fluid, double volume); public: - double add_heat(double m, double t); - double add_fluid(double amount, double heat); - double extract_fluid(double amount); + fluid_holder(fluid_t fluid, double volume, double extra_mass); + + const fluid_t fluid; + const double volume; // litres + const double extra_mass; // grams + + virtual double add_heat(double m, double t); + virtual double extract_fluid(double amount); + + virtual double add_fluid(double amount, double heat); virtual void add_steam(double amount, double t); - constexpr double get_volume() const { return volume; } // litres - constexpr double get_level() const { return level; } // litres - constexpr double get_heat() const { return heat; } // celsius - constexpr double get_steam() const { return steam; } // grams - constexpr double get_steam_volume() const { return volume - level; } // litres - constexpr double get_steam_density() const { return steam / get_steam_volume(); } // g/L - constexpr double get_mass() const { return fluid.l_to_g(level) + steam; } // grams + virtual double get_volume() const { return volume; } // litres + virtual double get_level() const { return level; } // litres + virtual double get_heat() const { return heat; } // celsius + virtual double get_steam() const { return steam; } // grams + virtual double get_steam_volume() const { return get_volume() - get_level(); } // litres + virtual double get_mass() const { return fluid.l_to_g(get_level()) + get_steam(); } // grams + virtual double get_thermal_mass() const { return get_mass() + extra_mass; } // grams + virtual double get_pressure() const; // pascals + virtual double get_steam_density() const; // g/L - double get_pressure() const; // pascals void update(double dt); }; diff --git a/src/coolant/fluid_t.hpp b/src/coolant/fluid_t.hpp index e153ca2..1e7ce8d 100644 --- a/src/coolant/fluid_t.hpp +++ b/src/coolant/fluid_t.hpp @@ -11,14 +11,12 @@ struct fluid_t const double gPl; // g/L const double gPmol; // g/mol const double jPg; // J/g latent heat of vaporisation - const double jPgk; // J/g/K heat capacity const double bubble_speed; // m/s const coolant::vapor_pressure vapor_pressure; - constexpr fluid_t(double gPl, double gPmol, double jPg, double jPgk, double bubble_speed, coolant::vapor_pressure vapor_pressure) : - gPl(gPl), gPmol(gPmol), - jPg(jPg), jPgk(jPgk), + constexpr fluid_t(double gPl, double gPmol, double jPg, double bubble_speed, coolant::vapor_pressure vapor_pressure) : + gPl(gPl), gPmol(gPmol), jPg(jPg), vapor_pressure(vapor_pressure), bubble_speed(bubble_speed) { @@ -33,7 +31,7 @@ struct fluid_t constexpr double l_to_mol(double l) const { return g_to_mol(l_to_g(l)); } }; -constexpr const fluid_t WATER = fluid_t(1000, 18, 2257, 4.1816, 0.3, {8.07131, 1730.63, 233.426}); +constexpr const fluid_t WATER = fluid_t(1000, 18, 2257, 4.1816, {8.07131, 1730.63, 233.426}); } diff --git a/src/coolant/pump.cpp b/src/coolant/pump.cpp index 5851266..8e92414 100644 --- a/src/coolant/pump.cpp +++ b/src/coolant/pump.cpp @@ -9,12 +9,12 @@ using namespace sim::coolant; pump::pump(fluid_holder* src, fluid_holder* dst, double mass, double radius, double l_per_rev, double friction) : src(src), dst(dst), mass(mass), radius(radius), l_per_rev(l_per_rev), friction(friction) { - power = 1e3; + power = 1e8; } double pump::get_flow() const { - return l_per_rev * get_rpm() / 60; + return src->fluid.l_to_g(l_per_rev * get_rpm() / 60); } double pump::get_rpm() const @@ -22,6 +22,26 @@ double pump::get_rpm() const return velocity / (M_PI * mass * 0.001 * radius * radius); } +const char* pump::get_state_string() +{ + if(!powered) + { + return "Off"; + } + + if(idling && std::abs(get_flow()) < 1e-3) + { + return "Idle"; + } + + if(idling) + { + return "Coasting"; + } + + return "Revving"; +} + static double calc_work(double j, double mass) { double m = 1; @@ -36,7 +56,10 @@ static double calc_work(double j, double mass) void pump::update(double dt) { - velocity += calc_work(dt * power, mass); + if(powered && !idling) + { + velocity += calc_work(dt * power, mass); + } double src_heat = src->get_heat(); double p_diff_1 = dst->get_pressure() - src->get_pressure(); @@ -54,14 +77,14 @@ void pump::update(double dt) velocity -= calc_work(work, mass); - if(dst->get_level() > 400 || src->get_level() < 50) + if(dst->get_level() > 400 || src->get_level() < 10) { - power = 0; + idling = true; } else { - power = 1e3; + idling = false; } std::cout << "RPM: " << get_rpm() << "\t"; diff --git a/src/coolant/pump.hpp b/src/coolant/pump.hpp index 261d7b9..526d8af 100644 --- a/src/coolant/pump.hpp +++ b/src/coolant/pump.hpp @@ -17,16 +17,19 @@ class pump const double friction; // J/rev double velocity = 0; // m/s + double power = 0; // W public: - double power = 0; // watts + bool powered = true; + bool idling = false; pump(fluid_holder* src, fluid_holder* dst, double mass, double radius, double l_per_rev, double friction); - double get_flow() const; // L/s + double get_flow() const; // g/s double get_rpm() const; // rev/min + const char* get_state_string(); void update(double dt); }; diff --git a/src/coolant/valve.cpp b/src/coolant/valve.cpp index 4d28159..ee793ea 100644 --- a/src/coolant/valve.cpp +++ b/src/coolant/valve.cpp @@ -1,14 +1,16 @@ #include "valve.hpp" +#include "../conversions/temperature.hpp" +#include "../constants.hpp" #include #include using namespace sim::coolant; -valve::valve(fluid_holder* src, fluid_holder* dst, double max) : src(src), dst(dst), max(max) +valve::valve(fluid_holder* src, fluid_holder* dst, double state, double max) : src(src), dst(dst), max(max) { - + this->state = state; } void valve::add_open_speed(double v) @@ -28,26 +30,53 @@ void valve::update(double dt) if(state > 1) state = 1; if(state < 0) state = 0; - double r = max * state; // L - double m = r * dt;//1 - std::pow(0.5, dt * -std::log2(1 - r)); - - double pressure1 = src->get_pressure(); - double pressure2 = dst->get_pressure(); - double density1 = src->get_steam_density(); // g/L - double density2 = dst->get_steam_density(); // g/L - double diff = (pressure1 - pressure2) * m; - double temp, mass; - - if(diff > 0) + if(src->get_steam_volume() == 0 || dst->get_steam_volume() == 0) { - temp = src->get_heat(); - mass = diff * src->get_steam_density(); + flow = 0; + return; } - else + double pressure1 = src->get_pressure(); // Pa + double pressure2 = dst->get_pressure(); + + int overshoots = 0; + double m = max * state * dt; + double temp, mass; + + for(;;) { - temp = dst->get_heat(); - mass = diff * dst->get_steam_density(); + double diff = (pressure1 - pressure2) * m; // L + + if(diff > 0) + { + temp = src->get_heat(); + mass = std::min(diff * src->get_steam_density(), src->get_steam()); + } + + else + { + temp = dst->get_heat(); + mass = std::min(diff * dst->get_steam_density(), dst->get_steam()); + } + + fluid_holder fh_src(*src); + fluid_holder fh_dst(*dst); + + fh_src.add_steam(-mass, temp); + fh_dst.add_steam(mass, temp); + +// if((pressure1 > fh_dst.get_pressure()) == (pressure2 < fh_src.get_pressure())) + { + break; + } + + overshoots += 1; + m *= 0.5; + } + + if(overshoots > 0) + { + std::cout << "Warning: overshot " << overshoots << " times\n"; } src->add_steam(-mass, temp); diff --git a/src/coolant/valve.hpp b/src/coolant/valve.hpp index 4b1cd9f..9d1c9f4 100644 --- a/src/coolant/valve.hpp +++ b/src/coolant/valve.hpp @@ -19,7 +19,7 @@ class valve public: - valve(fluid_holder* src, fluid_holder* dst, double max); + valve(fluid_holder* src, fluid_holder* dst, double state, double max); void update(double secs); void add_open_speed(double v); diff --git a/src/electric/turbine.cpp b/src/electric/turbine.cpp index 8f20159..d3ba6f4 100644 --- a/src/electric/turbine.cpp +++ b/src/electric/turbine.cpp @@ -14,32 +14,21 @@ constexpr static double calc_cylinder(double h, double d) return M_PI * r * r * h; } -turbine::turbine(coolant::fluid_t type, double height, double diameter, double level) : - height(height), diameter(diameter), sim::coolant::fluid_holder(type, calc_cylinder(height, diameter)) +turbine::turbine(coolant::fluid_t type, coolant::condenser* condenser, double length, double diameter, double mass) : + length(length), diameter(diameter), condenser(condenser), + sim::coolant::fluid_holder(type, calc_cylinder(length, diameter), mass) { this->level = level; } void turbine::update(double secs) { - sim::system& sys = sim::system::active; - auto& o = *sys.turbine.get(); + +} - ((sim::coolant::fluid_holder*)this)->update(secs); - -/* if(level + o.level > o.volume) - { - level += o.level - o.volume; - o.level = o.volume; - } - - else - { - o.level += level; - level = 0; - } - - balance_steam(o);*/ +void turbine::add_steam(double amount, double t) +{ + condenser->add_steam(amount, t); } diff --git a/src/electric/turbine.hpp b/src/electric/turbine.hpp index 035c7d9..e6c0c97 100644 --- a/src/electric/turbine.hpp +++ b/src/electric/turbine.hpp @@ -2,20 +2,38 @@ #pragma once #include "../coolant/fluid_holder.hpp" +#include "../coolant/condenser.hpp" namespace sim::electric { class turbine : public sim::coolant::fluid_holder { - const double height; + coolant::condenser* const condenser; + + const double length; const double diameter; public: - turbine(coolant::fluid_t type, double height, double diameter, double level); + turbine(coolant::fluid_t type, coolant::condenser* condenser, double length, double diameter, double mass); void update(double dt); + + virtual double add_heat(double m, double t) { return condenser->add_heat(m, t); } + virtual double extract_fluid(double amount) { return condenser->extract_fluid(amount); } + virtual double add_fluid(double amount, double heat) { return condenser->add_fluid(amount, heat); } + virtual void add_steam(double amount, double t); + + virtual double get_volume() const { return condenser->get_volume(); } + virtual double get_level() const { return condenser->get_level(); } + virtual double get_heat() const { return condenser->get_heat(); } // celsius + virtual double get_steam() const { return condenser->get_steam(); } // grams + virtual double get_steam_volume() const { return condenser->get_steam_volume(); } // litres + virtual double get_mass() const { return condenser->get_mass(); } // grams + virtual double get_thermal_mass() const { return condenser->get_thermal_mass(); } // grams + virtual double get_pressure() const { return condenser->get_pressure(); } // pascals + virtual double get_steam_density() const { return condenser->get_steam_density(); } // g/L }; }; diff --git a/src/graphics/monitor/primary_loop.cpp b/src/graphics/monitor/primary_loop.cpp index 7720471..8b36649 100644 --- a/src/graphics/monitor/primary_loop.cpp +++ b/src/graphics/monitor/primary_loop.cpp @@ -73,11 +73,8 @@ void primary_loop::init() ss << "Opened\nFlow\n\n"; ss << "Turbine Inlet Valve\n\n"; ss << "Opened\nFlow\n\n"; - ss << "Turbine\n\n"; - ss << "Heat\n"; - ss << "Steam\n"; - ss << "Pressure\n"; - ss << "Level\n\n"; + ss << "Primary Pump\n\n"; + ss << "Power\nSpeed\nFlow\n\n"; ss << "Condenser\n\n"; ss << "Heat\n"; ss << "Steam\n"; @@ -100,20 +97,19 @@ void primary_loop::update() ss << "\n\n"; ss << show( sys.turbine_bypass_valve->get_state() * 100 ) << " %\n"; - ss << show( sys.turbine_bypass_valve->get_flow() ) << " g/s\n"; + ss << show( sys.turbine_bypass_valve->get_flow() / 1000 ) << " kg/s\n"; ss << "\n\n\n"; ss << show( sys.turbine_inlet_valve->get_state() * 100 ) << " %\n"; - ss << show( sys.turbine_inlet_valve->get_flow() ) << " g/s\n"; + ss << show( sys.turbine_inlet_valve->get_flow() / 1000 ) << " kg/s\n"; ss << "\n\n\n"; - ss << show( sys.turbine->get_heat() ) << " C\n"; - ss << show( sys.turbine->get_steam() ) << " g\n"; - ss << show( sys.turbine->get_pressure() / 1000 ) << " kPa\n"; - ss << sys.turbine->get_level() << " / " << show( sys.turbine->get_volume() ) << " L\n"; + ss << sys.primary_pump->get_state_string() << "\n"; + ss << show( sys.primary_pump->get_rpm() ) << " r/min\n"; + ss << show( sys.primary_pump->get_flow() / 1000 ) << " kg/s\n"; ss << "\n\n\n"; ss << show( sys.condenser->get_heat() ) << " C\n"; ss << show( sys.condenser->get_steam() ) << " g\n"; ss << show( sys.condenser->get_pressure() / 1000 ) << " kPa\n"; - ss << sys.condenser->get_level() << " / " << show( sys.condenser->get_volume() ) << " L\n"; + ss << show( sys.condenser->get_level() ) << " / " << show( sys.condenser->get_volume() ) << " L\n"; rmesh.load_text(ss.str().c_str(), 0.04); mesh2.bind(); diff --git a/src/reactor/coolant/vessel.cpp b/src/reactor/coolant/vessel.cpp index ecd75d9..494baf5 100644 --- a/src/reactor/coolant/vessel.cpp +++ b/src/reactor/coolant/vessel.cpp @@ -16,8 +16,8 @@ constexpr static double calc_cylinder(double h, double d) return M_PI * r * r * h; } -vessel::vessel(sim::coolant::fluid_t fluid, double height, double diameter, double level) : - sim::coolant::fluid_holder(fluid, calc_cylinder(height, diameter)), +vessel::vessel(sim::coolant::fluid_t fluid, double height, double diameter, double mass, double level) : + sim::coolant::fluid_holder(fluid, calc_cylinder(height, diameter), mass), height(height), diameter(diameter) { this->level = level; diff --git a/src/reactor/coolant/vessel.hpp b/src/reactor/coolant/vessel.hpp index 1b10163..0eafa1a 100644 --- a/src/reactor/coolant/vessel.hpp +++ b/src/reactor/coolant/vessel.hpp @@ -16,7 +16,7 @@ public: const double diameter; // meters double steam_suspended = 0; // grams - vessel(sim::coolant::fluid_t fluid, double height, double diameter, double level); + vessel(sim::coolant::fluid_t fluid, double height, double diameter, double mass, double level); double get_steam_suspended() const; // grams double get_void_ratio() const; diff --git a/src/system.cpp b/src/system.cpp index 909fc30..215a535 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -35,14 +35,14 @@ system::system() " C C C C " }; - vessel = std::make_unique(sim::coolant::WATER, 8, 10, 300); + vessel = std::make_unique(sim::coolant::WATER, 8, 10, 6e6, 300); reactor = std::make_unique(sim::reactor::builder(19, 19, 1.0 / 4.0, 4, reactor::fuel::fuel_rod(0.5), vessel.get(), layout)); - condenser = std::make_unique(sim::coolant::WATER, 8, 6, 0); - turbine = std::make_unique(sim::coolant::WATER, 6, 3, 0); + condenser = std::make_unique(sim::coolant::WATER, 8, 6, 3e6, 0); + turbine = std::make_unique(sim::coolant::WATER, condenser.get(), 6, 3, 2e6); - turbine_inlet_valve = std::make_unique(vessel.get(), turbine.get(), 1e-7); - turbine_bypass_valve = std::make_unique(vessel.get(), condenser.get(), 1e-7); - core_pump = std::make_unique(condenser.get(), vessel.get(), 1e6, 1, 100, 100); + turbine_inlet_valve = std::make_unique(vessel.get(), turbine.get(), 0, 1e-2); + turbine_bypass_valve = std::make_unique(vessel.get(), condenser.get(), 0, 1e-2); + primary_pump = std::make_unique(condenser.get(), vessel.get(), 1e6, 1, 1, 0); } system::system(system&& o) @@ -53,18 +53,21 @@ system::system(system&& o) turbine = std::move(o.turbine); turbine_bypass_valve = std::move(o.turbine_bypass_valve); turbine_inlet_valve = std::move(o.turbine_inlet_valve); - core_pump = std::move(o.core_pump); + primary_pump = std::move(o.primary_pump); } void system::update(double dt) { dt *= speed; - vessel->update(dt); + reactor->update(dt); - condenser->update(dt); - turbine->update(dt); + vessel->update(dt); + turbine_inlet_valve->update(dt); turbine_bypass_valve->update(dt); -// core_pump->update(dt); + + turbine->update(dt); + condenser->update(dt); + primary_pump->update(dt); } diff --git a/src/system.hpp b/src/system.hpp index f3cfae0..f2aa8ba 100644 --- a/src/system.hpp +++ b/src/system.hpp @@ -22,7 +22,7 @@ struct system std::unique_ptr vessel; std::unique_ptr condenser; std::unique_ptr turbine; - std::unique_ptr core_pump; + std::unique_ptr primary_pump; std::unique_ptr turbine_bypass_valve; std::unique_ptr turbine_inlet_valve; sim::graphics::mesh scene; From 45bd74b7b0a37bdbd903fc6b226e46841fb1acb8 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Tue, 6 Feb 2024 01:08:44 +1100 Subject: [PATCH 43/43] pumps now work properly, even if they are very manual :) --- assets/model/primary_coolant_pump_switch.glb | 3 + assets/model/primary_coolant_pump_switch.stl | Bin 0 -> 1384 bytes .../model/secondary_coolant_pump_switch.glb | 3 + .../model/secondary_coolant_pump_switch.stl | Bin 0 -> 1384 bytes assets/scene-baked.glb | 4 +- assets/unbaked/scene.blend | 4 +- assets/unbaked/scene/labels.png | 4 +- assets/unbaked/scene/labels.xcf | 4 +- src/coolant/fluid_holder.cpp | 6 +- src/coolant/pump.cpp | 37 +++--- src/coolant/pump.hpp | 7 +- src/coolant/valve.cpp | 2 +- src/graphics/monitor/core.cpp | 8 +- src/graphics/monitor/primary_loop.cpp | 33 +++++- src/graphics/monitor/primary_loop.hpp | 7 ++ src/reactor/coolant/vessel.cpp | 2 +- src/reactor/reactor.cpp | 2 +- src/system.cpp | 4 +- src/{ => util}/constants.hpp | 0 src/util/pid.cpp | 112 ++++++++++++++++++ src/util/pid.hpp | 44 +++++++ src/{ => util}/random.cpp | 0 src/{ => util}/random.hpp | 0 23 files changed, 243 insertions(+), 43 deletions(-) create mode 100644 assets/model/primary_coolant_pump_switch.glb create mode 100644 assets/model/primary_coolant_pump_switch.stl create mode 100644 assets/model/secondary_coolant_pump_switch.glb create mode 100644 assets/model/secondary_coolant_pump_switch.stl rename src/{ => util}/constants.hpp (100%) create mode 100644 src/util/pid.cpp create mode 100644 src/util/pid.hpp rename src/{ => util}/random.cpp (100%) rename src/{ => util}/random.hpp (100%) diff --git a/assets/model/primary_coolant_pump_switch.glb b/assets/model/primary_coolant_pump_switch.glb new file mode 100644 index 0000000..7ce8622 --- /dev/null +++ b/assets/model/primary_coolant_pump_switch.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4344c73249a535c6472aaa177e75ecd94daef875444e9216fa3d03d50a9c8b2b +size 2300 diff --git a/assets/model/primary_coolant_pump_switch.stl b/assets/model/primary_coolant_pump_switch.stl new file mode 100644 index 0000000000000000000000000000000000000000..f14acb143f9cda172f2c05f0cd94ae83905b3b36 GIT binary patch literal 1384 zcmbW1u}%U(6h)`@4Xo(aKqg5?!5PQxfneS^XDvW4)Wnc^ZY)0Otbv>wB2c+xc___9x4`O?*@~k z+wCXQ@~}s}Y8`p~{+1@t@Wu(cKtrPvs~2AB2{r0Y%hD6f@Hsn#x~Q39^}*>0H8ap3 zGvWkYpgEDMj|DS$LKo`l+~S0~sM&{e>-E@oem$Qg2OoCdm&MANbxR)WtqHT~bD{F8ZL>-OwE}>Rg)V-0WugYFyFNx;iV} z&1QxY>Wbe=>vE#8om(GD=zKJ<{#M)?ra+Bn!8RC}(D}BD*@5_ZuKcsBo$Jo2_aoq9 oB@*GEf$5kJU8?sZn4z7Ax0d%sUEK$DmGS$QI9Er&#ovwi1}_`1B>(^b literal 0 HcmV?d00001 diff --git a/assets/model/secondary_coolant_pump_switch.glb b/assets/model/secondary_coolant_pump_switch.glb new file mode 100644 index 0000000..217e781 --- /dev/null +++ b/assets/model/secondary_coolant_pump_switch.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b78531b28fd2aa3a4187910a9d3e12589ce90bf618f1c6a06ba9dd87289eea4 +size 2304 diff --git a/assets/model/secondary_coolant_pump_switch.stl b/assets/model/secondary_coolant_pump_switch.stl new file mode 100644 index 0000000000000000000000000000000000000000..e7a7dfeac604e005bc1dea5e7e7a34ce8620fcf0 GIT binary patch literal 1384 zcmbW1u}%U(6h)`Dwvm`fNh2{Xv1MaI;y>7!xQmU7*-(&BxQ!oyEeW5XB|o6Gwebh2 zXwf^*yK|8pLZsn_x#!(^@9okXJq(Iro;CZ$;I4Uoo8M=7aolO2v`^iCJ`E3*bF!Ue znkLC*`aC)4QZHMFUcbL(DKxxsf-ca|sKn}}S9(H?x;OoqCz#=LHVAc5GsEhG(-UfD zpg(5B3A#XYB2^!AX7GeA)YZAg33XAk59d~^N8kC;(=^@tu=|epXB+zBv*CT)3Ak9v zh_CC#ncc0dPH=a)mMz<}?uLEfd!0}h``~m*8Ns^fgIae(cgU!7X`XYhSBn?p%C6Sc zS?O-pGn7zQ{8n0*6OHZM`cOjWqj~kW;?^()YCH?J!N7#h|GJnRh@a=mKfBtw?wooz q0xnh}5&jvNj``4~dN+a@+Ie_ud0*7keNa~!zi)|ibp%}e-H2~JWR(X1 literal 0 HcmV?d00001 diff --git a/assets/scene-baked.glb b/assets/scene-baked.glb index 5264c0c..f179956 100644 --- a/assets/scene-baked.glb +++ b/assets/scene-baked.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ed870c17367c0bb8858bb68fbc5277f56e47c3ffa650591b11d703e3052da9f -size 59404508 +oid sha256:28b515def72bcb7c1fb46ca68443b510565e9538032fd9320016953154f902f9 +size 59501212 diff --git a/assets/unbaked/scene.blend b/assets/unbaked/scene.blend index 2334421..cabd2ba 100644 --- a/assets/unbaked/scene.blend +++ b/assets/unbaked/scene.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ba3be1ad135735ed6ac111c2229004e8eb7d2d9442a873a5f49bd4cbc905f40 -size 10146216 +oid sha256:617293315153b7993db7beeaba58d2c30e5fc77e75b1d2447907541070357320 +size 12058488 diff --git a/assets/unbaked/scene/labels.png b/assets/unbaked/scene/labels.png index 7ae9d7b..3e2cb14 100644 --- a/assets/unbaked/scene/labels.png +++ b/assets/unbaked/scene/labels.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22b0bcb35c11d4da7718d85136baadbc5801d9823661e1feecd8851d384ef290 -size 26579 +oid sha256:9387fc6ea27299ea12fcc2b27240e9443c168989d5a642740567a4a4b5680745 +size 48112 diff --git a/assets/unbaked/scene/labels.xcf b/assets/unbaked/scene/labels.xcf index 823bd5d..ca10bf9 100644 --- a/assets/unbaked/scene/labels.xcf +++ b/assets/unbaked/scene/labels.xcf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28973e2b620fe500c7b820ea036ad81f65ff0181a597a480b9bcdf8ee4a5fe46 -size 81896 +oid sha256:faf2f6dc7b152f0322e515b3e859427cd0af4af13dcafdda50c84248cd804984 +size 140228 diff --git a/src/coolant/fluid_holder.cpp b/src/coolant/fluid_holder.cpp index 145aab1..e58454f 100644 --- a/src/coolant/fluid_holder.cpp +++ b/src/coolant/fluid_holder.cpp @@ -1,6 +1,6 @@ #include "fluid_holder.hpp" -#include "../constants.hpp" +#include "../util/constants.hpp" #include "../conversions/temperature.hpp" #include "../reactor/fuel/half_life.hpp" @@ -86,7 +86,7 @@ double fluid_holder::get_pressure() const if(V == 0) { - return NAN; + return 0; } return (n * T * constants::R) / V; @@ -95,7 +95,7 @@ double fluid_holder::get_pressure() const double fluid_holder::get_steam_density() const { double v = get_steam_volume(); - return v > 0 ? steam / v : NAN; + return v > 0 ? steam / v : 0; } void fluid_holder::update(double secs) diff --git a/src/coolant/pump.cpp b/src/coolant/pump.cpp index 8e92414..8291b94 100644 --- a/src/coolant/pump.cpp +++ b/src/coolant/pump.cpp @@ -6,15 +6,20 @@ using namespace sim::coolant; -pump::pump(fluid_holder* src, fluid_holder* dst, double mass, double radius, double l_per_rev, double friction) : +pump::pump(fluid_holder* src, fluid_holder* dst, double mass, double radius, double power, double l_per_rev, double friction) : src(src), dst(dst), mass(mass), radius(radius), l_per_rev(l_per_rev), friction(friction) { - power = 1e8; + this->power = power; } double pump::get_flow() const { - return src->fluid.l_to_g(l_per_rev * get_rpm() / 60); + return l_per_rev * get_rpm() * 60; +} + +double pump::get_flow_mass() const +{ + return src->fluid.l_to_g(get_flow()); } double pump::get_rpm() const @@ -56,40 +61,38 @@ static double calc_work(double j, double mass) void pump::update(double dt) { + idling = false; + if(powered && !idling) { velocity += calc_work(dt * power, mass); } + fluid_holder fh_src(*src); + fluid_holder fh_dst(*dst); + double src_heat = src->get_heat(); double p_diff_1 = dst->get_pressure() - src->get_pressure(); - double src_volume = src->extract_fluid(get_flow() * dt); - double dst_volume = dst->add_fluid(src_volume, src_heat); + double src_volume = fh_src.extract_fluid(get_flow() * dt); + double dst_volume = fh_dst.add_fluid(src_volume, src_heat); - if(dst_volume < src_volume) - { - src->add_fluid(src_volume - dst_volume, src_heat); - } + src->extract_fluid(dst_volume); + dst->add_fluid(dst_volume, src_heat); double p_diff_2 = dst->get_pressure() - src->get_pressure(); double p_diff = (p_diff_1 + p_diff_2) / 2; - double work = p_diff * dst_volume + get_rpm() * 60 * dt * friction; + double work = p_diff * dst_volume * 0.001 + get_rpm() * 60 * dt * friction; velocity -= calc_work(work, mass); if(dst->get_level() > 400 || src->get_level() < 10) { - idling = true; +// idling = true; } else { - idling = false; +// idling = false; } - - std::cout << "RPM: " << get_rpm() << "\t"; - std::cout << "Flow: " << get_flow() << std::endl; - std::cout << "Work Done: " << work << " J\n"; - std::cout << "Src Volume: " << src_volume << "\n"; } diff --git a/src/coolant/pump.hpp b/src/coolant/pump.hpp index 526d8af..30fda18 100644 --- a/src/coolant/pump.hpp +++ b/src/coolant/pump.hpp @@ -21,12 +21,13 @@ class pump public: - bool powered = true; + bool powered = false; bool idling = false; - pump(fluid_holder* src, fluid_holder* dst, double mass, double radius, double l_per_rev, double friction); + pump(fluid_holder* src, fluid_holder* dst, double mass, double radius, double power, double l_per_rev, double friction); - double get_flow() const; // g/s + double get_flow() const; // L/s + double get_flow_mass() const; // g/s double get_rpm() const; // rev/min const char* get_state_string(); diff --git a/src/coolant/valve.cpp b/src/coolant/valve.cpp index ee793ea..53cb9a9 100644 --- a/src/coolant/valve.cpp +++ b/src/coolant/valve.cpp @@ -1,7 +1,7 @@ #include "valve.hpp" #include "../conversions/temperature.hpp" -#include "../constants.hpp" +#include "../util/constants.hpp" #include #include diff --git a/src/graphics/monitor/core.cpp b/src/graphics/monitor/core.cpp index 12054b1..1cbe0a1 100644 --- a/src/graphics/monitor/core.cpp +++ b/src/graphics/monitor/core.cpp @@ -73,16 +73,14 @@ struct core_monitor : public focus::focus_t struct core_joystick : public focus::focus_t { - double ypos = 0; - virtual void on_cursor_pos(double x, double y) { - ypos += y; + sim::system::active.reactor->add_rod_speed(y * 1e-6); } - virtual void update(double dt) + virtual ~core_joystick() { - sim::system::active.reactor->add_rod_speed(ypos * dt * 1e-6); + sim::system::active.reactor->reset_rod_speed(); } virtual void on_mouse_button(int button, int action, int mods) diff --git a/src/graphics/monitor/primary_loop.cpp b/src/graphics/monitor/primary_loop.cpp index 8b36649..961eb8b 100644 --- a/src/graphics/monitor/primary_loop.cpp +++ b/src/graphics/monitor/primary_loop.cpp @@ -54,6 +54,15 @@ primary_loop::primary_loop() } +void primary_loop::toggle_primary_pump() +{ + system& sys = sim::system::active; + bool state; + + sys.primary_pump->powered = state = !sys.primary_pump->powered; + gm_switch_primary.model_matrix = glm::translate(glm::mat4(1), glm::vec3(0, state ? 0.07 : 0, 0)); +} + void primary_loop::init() { mesh1.model_matrix = locations::monitors[3]; @@ -85,8 +94,18 @@ void primary_loop::init() mesh1.bind(); mesh1.set(rmesh, GL_STATIC_DRAW); + rmesh.load_model("../assets/model", "primary_coolant_pump_switch.glb"); + gm_switch_primary.bind(); + gm_switch_primary.set(rmesh, GL_STATIC_DRAW); + + rmesh.load_model("../assets/model", "secondary_coolant_pump_switch.glb"); + gm_switch_secondary.bind(); + gm_switch_secondary.set(rmesh, GL_STATIC_DRAW); + m_joystick_turbine_bypass.load_model("../assets/model", "turbine_valve_bypass_joystick.stl"); m_joystick_turbine_inlet.load_model("../assets/model", "turbine_valve_inlet_joystick.stl"); + m_switch_primary.load_model("../assets/model", "primary_coolant_pump_switch.stl"); + m_switch_secondary.load_model("../assets/model", "secondary_coolant_pump_switch.stl"); } void primary_loop::update() @@ -104,7 +123,7 @@ void primary_loop::update() ss << "\n\n\n"; ss << sys.primary_pump->get_state_string() << "\n"; ss << show( sys.primary_pump->get_rpm() ) << " r/min\n"; - ss << show( sys.primary_pump->get_flow() / 1000 ) << " kg/s\n"; + ss << show( sys.primary_pump->get_flow_mass() / 1000 ) << " kg/s\n"; ss << "\n\n\n"; ss << show( sys.condenser->get_heat() ) << " C\n"; ss << show( sys.condenser->get_steam() ) << " g\n"; @@ -114,11 +133,13 @@ void primary_loop::update() rmesh.load_text(ss.str().c_str(), 0.04); mesh2.bind(); mesh2.set(rmesh, GL_DYNAMIC_DRAW); - + if(m_joystick_turbine_bypass.check_focus()) focus::set(std::make_unique(sys.turbine_bypass_valve.get())); if(m_joystick_turbine_inlet.check_focus()) focus::set(std::make_unique(sys.turbine_inlet_valve.get())); + if(m_switch_primary.check_focus()) + toggle_primary_pump(); } void primary_loop::render() @@ -130,5 +151,13 @@ void primary_loop::render() mesh2.bind(); mesh2.uniform(); mesh2.render(); + + gm_switch_primary.bind(); + gm_switch_primary.uniform(); + gm_switch_primary.render(); + + gm_switch_secondary.bind(); + gm_switch_secondary.uniform(); + gm_switch_secondary.render(); } diff --git a/src/graphics/monitor/primary_loop.hpp b/src/graphics/monitor/primary_loop.hpp index 5982126..eb56eab 100644 --- a/src/graphics/monitor/primary_loop.hpp +++ b/src/graphics/monitor/primary_loop.hpp @@ -9,9 +9,16 @@ namespace sim::graphics::monitor class primary_loop { sim::graphics::glmesh mesh1, mesh2; + + sim::graphics::glmesh gm_switch_primary; + sim::graphics::glmesh gm_switch_secondary; sim::graphics::mesh m_joystick_turbine_bypass; sim::graphics::mesh m_joystick_turbine_inlet; + sim::graphics::mesh m_switch_primary; + sim::graphics::mesh m_switch_secondary; + + void toggle_primary_pump(); public: diff --git a/src/reactor/coolant/vessel.cpp b/src/reactor/coolant/vessel.cpp index 494baf5..a3dcc96 100644 --- a/src/reactor/coolant/vessel.cpp +++ b/src/reactor/coolant/vessel.cpp @@ -1,6 +1,6 @@ #include "vessel.hpp" -#include "../../constants.hpp" +#include "../../util/constants.hpp" #include "../../conversions/temperature.hpp" #include "../fuel/half_life.hpp" diff --git a/src/reactor/reactor.cpp b/src/reactor/reactor.cpp index ced8e84..db5448c 100644 --- a/src/reactor/reactor.cpp +++ b/src/reactor/reactor.cpp @@ -1,6 +1,6 @@ #include "reactor.hpp" -#include "../random.hpp" +#include "../util/random.hpp" #include diff --git a/src/system.cpp b/src/system.cpp index 215a535..0805a3f 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -37,12 +37,12 @@ system::system() vessel = std::make_unique(sim::coolant::WATER, 8, 10, 6e6, 300); reactor = std::make_unique(sim::reactor::builder(19, 19, 1.0 / 4.0, 4, reactor::fuel::fuel_rod(0.5), vessel.get(), layout)); - condenser = std::make_unique(sim::coolant::WATER, 8, 6, 3e6, 0); + condenser = std::make_unique(sim::coolant::WATER, 8, 6, 3e6, 200); turbine = std::make_unique(sim::coolant::WATER, condenser.get(), 6, 3, 2e6); turbine_inlet_valve = std::make_unique(vessel.get(), turbine.get(), 0, 1e-2); turbine_bypass_valve = std::make_unique(vessel.get(), condenser.get(), 0, 1e-2); - primary_pump = std::make_unique(condenser.get(), vessel.get(), 1e6, 1, 1, 0); + primary_pump = std::make_unique(condenser.get(), vessel.get(), 1e6, 1, 1e6, 1, 10); } system::system(system&& o) diff --git a/src/constants.hpp b/src/util/constants.hpp similarity index 100% rename from src/constants.hpp rename to src/util/constants.hpp diff --git a/src/util/pid.cpp b/src/util/pid.cpp new file mode 100644 index 0000000..a5b9ca8 --- /dev/null +++ b/src/util/pid.cpp @@ -0,0 +1,112 @@ +/** + * Copyright 2019 Bradley J. Snyder + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include "pid.hpp" + +using namespace std; + +class PIDImpl +{ + public: + PIDImpl( double dt, double max, double min, double Kp, double Kd, double Ki ); + ~PIDImpl(); + double calculate( double setpoint, double pv ); + + private: + double _dt; + double _max; + double _min; + double _Kp; + double _Kd; + double _Ki; + double _pre_error; + double _integral; +}; + + +PID::PID( double dt, double max, double min, double Kp, double Kd, double Ki ) +{ + pimpl = new PIDImpl(dt,max,min,Kp,Kd,Ki); +} +double PID::calculate( double setpoint, double pv ) +{ + return pimpl->calculate(setpoint,pv); +} +PID::~PID() +{ + delete pimpl; +} + + +/** + * Implementation + */ +PIDImpl::PIDImpl( double dt, double max, double min, double Kp, double Kd, double Ki ) : + _dt(dt), + _max(max), + _min(min), + _Kp(Kp), + _Kd(Kd), + _Ki(Ki), + _pre_error(0), + _integral(0) +{ +} + +double PIDImpl::calculate( double setpoint, double pv ) +{ + + // Calculate error + double error = setpoint - pv; + + // Proportional term + double Pout = _Kp * error; + + // Integral term + _integral += error * _dt; + double Iout = _Ki * _integral; + + // Derivative term + double derivative = (error - _pre_error) / _dt; + double Dout = _Kd * derivative; + + // Calculate total output + double output = Pout + Iout + Dout; + + // Restrict to max/min + if( output > _max ) + output = _max; + else if( output < _min ) + output = _min; + + // Save error to previous error + _pre_error = error; + + return output; +} + +PIDImpl::~PIDImpl() +{ +} + diff --git a/src/util/pid.hpp b/src/util/pid.hpp new file mode 100644 index 0000000..c537f20 --- /dev/null +++ b/src/util/pid.hpp @@ -0,0 +1,44 @@ +/** + * Copyright 2019 Bradley J. Snyder + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +class PIDImpl; +class PID +{ + public: + // Kp - proportional gain + // Ki - Integral gain + // Kd - derivative gain + // dt - loop interval time + // max - maximum value of manipulated variable + // min - minimum value of manipulated variable + PID( double dt, double max, double min, double Kp, double Kd, double Ki ); + + // Returns the manipulated variable given a setpoint and current process value + double calculate( double setpoint, double pv ); + ~PID(); + + private: + PIDImpl *pimpl; +}; + diff --git a/src/random.cpp b/src/util/random.cpp similarity index 100% rename from src/random.cpp rename to src/util/random.cpp diff --git a/src/random.hpp b/src/util/random.hpp similarity index 100% rename from src/random.hpp rename to src/util/random.hpp