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 2475ac7..e27ce7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build +*.blend1 diff --git a/CMakeLists.txt b/CMakeLists.txt index f1fad78..b09541e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ 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_STANDARD 26) +set(CMAKE_CXX_FLAGS "-g -O3 -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 assimp) diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a8595f --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ + +# How to build + +This is built using CMake. You will also need the required libraries to build. +This project currently is only tested on Linux. + +``` +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/). + diff --git a/assets/font/DroidSans.ttf b/assets/font/DroidSans.ttf new file mode 100644 index 0000000..ad1efca Binary files /dev/null and b/assets/font/DroidSans.ttf differ 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 0000000..f14acb1 Binary files /dev/null and b/assets/model/primary_coolant_pump_switch.stl differ diff --git a/assets/model/reactor_core_button1.stl b/assets/model/reactor_core_button1.stl new file mode 100644 index 0000000..7e26a5c Binary files /dev/null and b/assets/model/reactor_core_button1.stl differ diff --git a/assets/model/reactor_core_button2.stl b/assets/model/reactor_core_button2.stl new file mode 100644 index 0000000..7166900 Binary files /dev/null and b/assets/model/reactor_core_button2.stl differ diff --git a/assets/model/reactor_core_button3.stl b/assets/model/reactor_core_button3.stl new file mode 100644 index 0000000..1cb9d84 Binary files /dev/null and b/assets/model/reactor_core_button3.stl differ diff --git a/assets/model/reactor_core_button4.stl b/assets/model/reactor_core_button4.stl new file mode 100644 index 0000000..7b94757 Binary files /dev/null and b/assets/model/reactor_core_button4.stl differ diff --git a/assets/model/reactor_core_button5.stl b/assets/model/reactor_core_button5.stl new file mode 100644 index 0000000..a3539e2 Binary files /dev/null and b/assets/model/reactor_core_button5.stl differ diff --git a/assets/model/reactor_core_button6.stl b/assets/model/reactor_core_button6.stl new file mode 100644 index 0000000..50e62fe Binary files /dev/null and b/assets/model/reactor_core_button6.stl differ diff --git a/assets/model/reactor_core_button7.stl b/assets/model/reactor_core_button7.stl new file mode 100644 index 0000000..d0f5dc5 Binary files /dev/null and b/assets/model/reactor_core_button7.stl differ diff --git a/assets/model/reactor_core_button8.stl b/assets/model/reactor_core_button8.stl new file mode 100644 index 0000000..7ce1330 Binary files /dev/null and b/assets/model/reactor_core_button8.stl differ diff --git a/assets/model/reactor_core_button9.stl b/assets/model/reactor_core_button9.stl new file mode 100644 index 0000000..a785ff8 Binary files /dev/null and b/assets/model/reactor_core_button9.stl differ diff --git a/assets/model/reactor_core_input.stl b/assets/model/reactor_core_input.stl new file mode 100644 index 0000000..7d86b99 Binary files /dev/null and b/assets/model/reactor_core_input.stl differ 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 0000000..3d05d78 Binary files /dev/null and b/assets/model/reactor_core_interface_cell.stl differ 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 0000000..8193953 Binary files /dev/null and b/assets/model/reactor_core_interface_circle.stl differ diff --git a/assets/model/reactor_core_joystick.stl b/assets/model/reactor_core_joystick.stl new file mode 100644 index 0000000..9272a14 Binary files /dev/null and b/assets/model/reactor_core_joystick.stl differ diff --git a/assets/model/reactor_core_scram.stl b/assets/model/reactor_core_scram.stl new file mode 100644 index 0000000..35f8899 Binary files /dev/null and b/assets/model/reactor_core_scram.stl differ diff --git a/assets/model/scene_collisions.stl b/assets/model/scene_collisions.stl new file mode 100644 index 0000000..eee4323 Binary files /dev/null and b/assets/model/scene_collisions.stl differ 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 0000000..e7a7dfe Binary files /dev/null and b/assets/model/secondary_coolant_pump_switch.stl differ diff --git a/assets/model/turbine_valve_bypass_joystick.stl b/assets/model/turbine_valve_bypass_joystick.stl new file mode 100644 index 0000000..08e6640 Binary files /dev/null and b/assets/model/turbine_valve_bypass_joystick.stl differ diff --git a/assets/model/turbine_valve_inlet_joystick.stl b/assets/model/turbine_valve_inlet_joystick.stl new file mode 100644 index 0000000..dfe90a2 Binary files /dev/null and b/assets/model/turbine_valve_inlet_joystick.stl differ diff --git a/assets/scene-baked.glb b/assets/scene-baked.glb new file mode 100644 index 0000000..f179956 --- /dev/null +++ b/assets/scene-baked.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28b515def72bcb7c1fb46ca68443b510565e9538032fd9320016953154f902f9 +size 59501212 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..d7d62c3 --- /dev/null +++ b/assets/shader/main.vsh @@ -0,0 +1,29 @@ + +#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 camera; +uniform mat4 projection; + +out float brightness; +out flat sampler2D tex; +out vec2 texPos; + +void main() +{ + 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; + + 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..cabd2ba --- /dev/null +++ b/assets/unbaked/scene.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:617293315153b7993db7beeaba58d2c30e5fc77e75b1d2447907541070357320 +size 12058488 diff --git a/assets/unbaked/scene.stl b/assets/unbaked/scene.stl new file mode 100644 index 0000000..eee4323 Binary files /dev/null and b/assets/unbaked/scene.stl differ 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/assets/unbaked/scene/labels.png b/assets/unbaked/scene/labels.png new file mode 100644 index 0000000..3e2cb14 --- /dev/null +++ b/assets/unbaked/scene/labels.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9387fc6ea27299ea12fcc2b27240e9443c168989d5a642740567a4a4b5680745 +size 48112 diff --git a/assets/unbaked/scene/labels.xcf b/assets/unbaked/scene/labels.xcf new file mode 100644 index 0000000..ca10bf9 --- /dev/null +++ b/assets/unbaked/scene/labels.xcf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:faf2f6dc7b152f0322e515b3e859427cd0af4af13dcafdda50c84248cd804984 +size 140228 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 diff --git a/src/coolant/condenser.cpp b/src/coolant/condenser.cpp new file mode 100644 index 0000000..d77aae7 --- /dev/null +++ b/src/coolant/condenser.cpp @@ -0,0 +1,27 @@ + +#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 mass, double level) : + height(height), diameter(diameter), fluid_holder(type, calc_cylinder(height, diameter), mass) +{ + this->level = level; +} + +void condenser::update(double secs) +{ + ((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..4268ea1 --- /dev/null +++ b/src/coolant/condenser.hpp @@ -0,0 +1,22 @@ + +#pragma once + +#include "fluid_holder.hpp" + +namespace sim::coolant +{ + +class condenser : public fluid_holder +{ + const double height; + const double diameter; + +public: + + 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 new file mode 100644 index 0000000..e58454f --- /dev/null +++ b/src/coolant/fluid_holder.cpp @@ -0,0 +1,135 @@ + +#include "fluid_holder.hpp" +#include "../util/constants.hpp" +#include "../conversions/temperature.hpp" +#include "../reactor/fuel/half_life.hpp" + +#include +#include + +using namespace sim::coolant; + +fluid_holder::fluid_holder(fluid_t fluid, double volume, double extra_mass) : fluid(fluid), volume(volume), extra_mass(extra_mass) +{ + +} + +double fluid_holder::add_heat(double m1, double t1) +{ + double t2 = get_heat(); + double t = t1 - t2; + double m2 = get_thermal_mass(); + double m = m1 + m2; + + if(m1 == 0 || m2 == 0) + return t1; + + heat = t1 - t * m2 / m; + + return heat; +} + +double fluid_holder::add_fluid(double v2, double t2) +{ + if(level + v2 > volume) + { + v2 = volume - level; + } + + 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; +} + +double fluid_holder::extract_fluid(double amount) +{ + if(amount < level) + { + level -= amount; + } + + else + { + amount = level; + level = 0; + } + + return amount; +} + +void fluid_holder::add_steam(double m2, double t2) +{ + double m1 = get_thermal_mass(); + double t1 = heat; + double m = m1 + m2; + + if(m > 0) + { + heat = t1 - (t1 - t2) * m2 / (m1 + m2); + } + + steam += m2; +} + +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 0; + } + + return (n * T * constants::R) / V; +} + +double fluid_holder::get_steam_density() const +{ + double v = get_steam_volume(); + return v > 0 ? steam / v : 0; +} + +void fluid_holder::update(double secs) +{ + double mass = get_thermal_mass(); + + if(mass > 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; + } +} + diff --git a/src/coolant/fluid_holder.hpp b/src/coolant/fluid_holder.hpp new file mode 100644 index 0000000..8272f5c --- /dev/null +++ b/src/coolant/fluid_holder.hpp @@ -0,0 +1,45 @@ + +#pragma once + +#include "fluid_t.hpp" + +namespace sim::coolant +{ + +class fluid_holder +{ +protected: + + double level = 0; // litres + double steam = 0; // grams + double heat = 0; // celsius + +public: + + 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); + + 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 + + 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 new file mode 100644 index 0000000..8291b94 --- /dev/null +++ b/src/coolant/pump.cpp @@ -0,0 +1,98 @@ + +#include "pump.hpp" + +#include +#include + +using namespace sim::coolant; + +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) +{ + this->power = power; +} + +double pump::get_flow() const +{ + 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 +{ + 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; + + if(j < 0) + { + m = -1; + } + + return m * std::sqrt(m * j / (mass * 0.001)); +} + +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 = fh_src.extract_fluid(get_flow() * dt); + double dst_volume = fh_dst.add_fluid(src_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 * 0.001 + get_rpm() * 60 * dt * friction; + + velocity -= calc_work(work, mass); + + if(dst->get_level() > 400 || src->get_level() < 10) + { +// idling = true; + } + + else + { +// idling = false; + } +} + diff --git a/src/coolant/pump.hpp b/src/coolant/pump.hpp index a1f928c..30fda18 100644 --- a/src/coolant/pump.hpp +++ b/src/coolant/pump.hpp @@ -1,57 +1,38 @@ #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 + double power = 0; // W public: - constexpr pump(A& a, double max, double heat) : a(&a), max(max), heat(heat) - { + bool powered = false; + bool idling = false; - } + pump(fluid_holder* src, fluid_holder* dst, double mass, double radius, double power, double l_per_rev, double friction); - constexpr double get_rate() const - { - return rate; - } + double get_flow() const; // L/s + double get_flow_mass() const; // g/s + double get_rpm() const; // rev/min + + const char* get_state_string(); + 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..53cb9a9 --- /dev/null +++ b/src/coolant/valve.cpp @@ -0,0 +1,87 @@ + +#include "valve.hpp" +#include "../conversions/temperature.hpp" +#include "../util/constants.hpp" + +#include +#include + +using namespace sim::coolant; + +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) +{ + speed += v; +} + +void valve::clear_open_speed() +{ + speed = 0; +} + +void valve::update(double dt) +{ + state += speed * dt; + + if(state > 1) state = 1; + if(state < 0) state = 0; + + if(src->get_steam_volume() == 0 || dst->get_steam_volume() == 0) + { + flow = 0; + return; + } + + double pressure1 = src->get_pressure(); // Pa + double pressure2 = dst->get_pressure(); + + int overshoots = 0; + double m = max * state * dt; + double temp, mass; + + for(;;) + { + 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); + dst->add_steam(mass, temp); + + this->flow = mass / dt; +} + diff --git a/src/coolant/valve.hpp b/src/coolant/valve.hpp index 8498832..9d1c9f4 100644 --- a/src/coolant/valve.hpp +++ b/src/coolant/valve.hpp @@ -1,55 +1,32 @@ #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 speed = 0; double state = 0; - double rate = 0; + double flow = 0; // L/s public: - constexpr valve(A& a, double max, double pressure) : a(&a), max(max), pressure(pressure) - { + valve(fluid_holder* src, fluid_holder* dst, double state, 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 update(double secs); + void add_open_speed(double v); + void clear_open_speed(); + + constexpr double get_state() const { return state; } + constexpr double get_flow() const { return flow; } }; }; 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 new file mode 100644 index 0000000..d3ba6f4 --- /dev/null +++ b/src/electric/turbine.cpp @@ -0,0 +1,34 @@ + +#include "turbine.hpp" +#include "../system.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, 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) +{ + +} + +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 new file mode 100644 index 0000000..e6c0c97 --- /dev/null +++ b/src/electric/turbine.hpp @@ -0,0 +1,40 @@ + +#pragma once + +#include "../coolant/fluid_holder.hpp" +#include "../coolant/condenser.hpp" + +namespace sim::electric +{ + +class turbine : public sim::coolant::fluid_holder +{ + coolant::condenser* const condenser; + + const double length; + const double diameter; + +public: + + 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/camera.cpp b/src/graphics/camera.cpp new file mode 100644 index 0000000..750d637 --- /dev/null +++ b/src/graphics/camera.cpp @@ -0,0 +1,109 @@ + +#include +#include + +#include "camera.hpp" +#include "input/keyboard.hpp" +#include "../util/math.hpp" + +#include +#include +#include +#include +#include + +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); +static glm::mat4 camera_mat; + +void camera::rotate(double y, double p) +{ + yaw += y * 0.05; + pitch -= p * 0.05; + + if(pitch < 0) pitch = 0; + if(pitch > 180) pitch = 180; +} + +void camera::move(double xoff, double yoff, double zoff) +{ + pos.x += xoff; + pos.y += yoff; + 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(double dt) +{ + glm::vec<2, double> off(0, 0); + double m = 30; + + if(keyboard::is_pressed(GLFW_KEY_W)) + off.y += 1; + 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_LEFT_SHIFT)) + m *= 1.5; + if(off.x != 0 || off.y != 0) + off = glm::normalize(off); + + 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.y) * mat; + velocity.z -= 9.81 * dt; + + if(on_ground) + { + 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; + } + + glm::vec<3, double> normal_last(0); + glm::vec<3, double> velocity2; + + 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); + 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)); + 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)); +} + +glm::mat4 camera::get_matrix() +{ + return camera_mat; +} + diff --git a/src/graphics/camera.hpp b/src/graphics/camera.hpp new file mode 100644 index 0000000..fb9a1a4 --- /dev/null +++ b/src/graphics/camera.hpp @@ -0,0 +1,20 @@ + +#pragma once + +#include + +#include "../system.hpp" + +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(double dt); + +}; + diff --git a/src/graphics/input/focus.cpp b/src/graphics/input/focus.cpp new file mode 100644 index 0000000..459a6ca --- /dev/null +++ b/src/graphics/input/focus.cpp @@ -0,0 +1,180 @@ + +#include +#include + +#include "focus.hpp" +#include "../../util/math.hpp" +#include "../window.hpp" +#include "../camera.hpp" +#include "../resize.hpp" +#include "mouse.hpp" + +#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; +static bool triggered = false; + +void focus::on_keypress(int key, int sc, int action, int mods) +{ + if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) + { + if(is_mouse_locked()) + { + clear_mouse_locked(); + } + + else + { + mouse_locked = true; + } + } + + 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); + } + + if(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) + { + if(is_mouse_locked() && mouse_visible) + { + 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); + triggered = true; + } + + else if(!mouse_visible) + { + trigger_near = camera::get_pos(); + trigger_far = trigger_near + camera::get_normal(); + 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); + } +} + +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(double dt) +{ + triggered = false; + + bool c = is_mouse_locked(); + + if(state && !state->cursor_is_visible()) + { + c = false; + } + + if(c != mouse_visible) + { + if(c) + { + mouse::show_cursor(); + } + + else + { + mouse::hide_cursor(); + } + + mouse_visible = c; + } + + if(state) + { + state->update(dt); + } +} + +bool focus::is_focused() +{ + return (state != nullptr); +} + +void focus::clear_focus() +{ + state = nullptr; + + if(stack.size() != 0) + { + state = std::move(stack.back()); + stack.pop_back(); + } +} + +bool focus::is_mouse_locked() +{ + return is_focused() || mouse_locked; +} + +void focus::clear_mouse_locked() +{ + mouse_locked = false; + clear_focus(); +} + +void focus::set(std::unique_ptr f) +{ + if(state != nullptr) + { + stack.push_back(std::move(state)); + } + + state = std::move(f); +} + +bool focus::is_triggered() +{ + return triggered; +} + diff --git a/src/graphics/input/focus.hpp b/src/graphics/input/focus.hpp new file mode 100644 index 0000000..ad8f5cb --- /dev/null +++ b/src/graphics/input/focus.hpp @@ -0,0 +1,37 @@ + +#pragma once + +#include + +#include + +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) { } + virtual void on_charcode(unsigned int c) { } + virtual void update(double dt) { } +}; + +bool is_focused(); +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); +void on_cursor_pos(double x, double y); +void on_charcode(unsigned int c); +void update(double dt); + +}; + diff --git a/src/graphics/input/keyboard.cpp b/src/graphics/input/keyboard.cpp new file mode 100644 index 0000000..391999b --- /dev/null +++ b/src/graphics/input/keyboard.cpp @@ -0,0 +1,88 @@ + +#include +#include + +#include + +#include "focus.hpp" +#include "keyboard.hpp" +#include "../window.hpp" +#include "../resize.hpp" +#include "../camera.hpp" +#include "../../system.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; + + 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) + { + 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_mouse_locked()) + { + return false; + } + + auto it = pressed.find(key); + + if(it == pressed.end()) + { + return false; + } + + else + { + return it->second; + } +} + +void keyboard::init() +{ + GLFWwindow* win = window::get_window(); + glfwSetKeyCallback(win, cb_keypress); + glfwSetCharCallback(win, cb_charcode); +} + diff --git a/src/graphics/input/keyboard.hpp b/src/graphics/input/keyboard.hpp new file mode 100644 index 0000000..9762383 --- /dev/null +++ b/src/graphics/input/keyboard.hpp @@ -0,0 +1,11 @@ + +#pragma once + +namespace sim::graphics::keyboard +{ + +void init(); +bool is_pressed(int key); + +}; + diff --git a/src/graphics/input/mouse.cpp b/src/graphics/input/mouse.cpp new file mode 100644 index 0000000..4403e11 --- /dev/null +++ b/src/graphics/input/mouse.cpp @@ -0,0 +1,72 @@ + +#include +#include + +#include "focus.hpp" +#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) +{ + if(focus::is_mouse_locked()) + { + focus::on_cursor_pos(x - xpos, y - ypos); + } + + else + { + 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; + y = ypos; +} + +glm::vec2 mouse::get() +{ + return {xpos, 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(); + 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/input/mouse.hpp b/src/graphics/input/mouse.hpp new file mode 100644 index 0000000..8d2dc86 --- /dev/null +++ b/src/graphics/input/mouse.hpp @@ -0,0 +1,16 @@ + +#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/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/arrays.cpp b/src/graphics/mesh/arrays.cpp new file mode 100644 index 0000000..4d6978f --- /dev/null +++ b/src/graphics/mesh/arrays.cpp @@ -0,0 +1,46 @@ + +#include +#include + +#include + +#include "../shader.hpp" +#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); +} + +void arrays::vertex_attrib_pointers() +{ + vertex v; + + 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, 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)); + 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 new file mode 100644 index 0000000..1b6f41c --- /dev/null +++ b/src/graphics/mesh/arrays.hpp @@ -0,0 +1,21 @@ + +#pragma once + +#include + +namespace sim::graphics::arrays +{ + +struct vertex +{ + unsigned long texid = 0; + glm::vec2 texpos = {0, 0}; + glm::vec4 pos = {0, 0, 0, 1}; + glm::vec3 normal = {0, 0, 0}; +}; + +void vertex_attrib_pointers(); +glm::mat4 colour(glm::vec4 code); + +}; + diff --git a/src/graphics/mesh/font.cpp b/src/graphics/mesh/font.cpp new file mode 100644 index 0000000..0db843a --- /dev/null +++ b/src/graphics/mesh/font.cpp @@ -0,0 +1,145 @@ + +#include +#include + +#include +#include FT_FREETYPE_H + +#include +#include +#include + +#include "mesh.hpp" +#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 = 256; + 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; + } + + FT_Done_FreeType(ft); +} + +void mesh::load_text(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, 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; + x += ch.advance * size; + } + + set_vertices(&vertices[0], vertices.size()); + set_indices(&indices[0], indices.size()); +} + diff --git a/src/graphics/mesh/font.hpp b/src/graphics/mesh/font.hpp new file mode 100644 index 0000000..52b6ee0 --- /dev/null +++ b/src/graphics/mesh/font.hpp @@ -0,0 +1,15 @@ + +#pragma once + +#include "mesh.hpp" + +#include +#include + +namespace sim::graphics::font +{ + +void init(); + +}; + diff --git a/src/graphics/mesh/glmesh.cpp b/src/graphics/mesh/glmesh.cpp new file mode 100644 index 0000000..fe8bca1 --- /dev/null +++ b/src/graphics/mesh/glmesh.cpp @@ -0,0 +1,82 @@ + +#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() +{ + glUniformMatrix4fv(shader::gl_model, 1, false, &model_matrix[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() +{ + 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 new file mode 100644 index 0000000..b8a2223 --- /dev/null +++ b/src/graphics/mesh/glmesh.hpp @@ -0,0 +1,35 @@ + +#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(int type); + void render(); +}; + +}; + diff --git a/src/graphics/mesh/mesh.cpp b/src/graphics/mesh/mesh.cpp new file mode 100644 index 0000000..01c9b39 --- /dev/null +++ b/src/graphics/mesh/mesh.cpp @@ -0,0 +1,257 @@ + +#include "mesh.hpp" +#include "arrays.hpp" +#include "../shader.hpp" +#include "../camera.hpp" +#include "../input/focus.hpp" +#include "../../util/math.hpp" + +#include + +using namespace sim::graphics; + +void mesh::add(const mesh& o, glm::mat4 mat) +{ + 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++) + { + arrays::vertex v = o.vertices[i]; + v.normal = v.normal * mat3; + v.pos = v.pos * mat; + vertices.push_back(v); + } + + for(unsigned int i = 0; i < o.indices.size(); i++) + { + indices.push_back(o.indices[i] + off); + } +} + +void mesh::set_vertices(const arrays::vertex* data, size_t size) +{ + vertices.clear(); + vertices.reserve(size); + + for(unsigned int i = 0; i < size; i++) + { + vertices.push_back(data[i]); + } +} + +void mesh::set_indices(const unsigned int* data, size_t size) +{ + indices.clear(); + indices.reserve(size); + + for(unsigned int i = 0; i < size; i++) + { + indices.push_back(data[i]); + } +} + +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; +} + +bool mesh::check_focus(double len) const +{ + 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 +{ + return check_focus(2.5); +} + +bool mesh::check_intersect(vec3 pos, vec3 path) const +{ + double l = glm::length(path); + + if(l == 0) + { + return false; + } + + vec3 path_n = path / l; + + 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; + + 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); + + if(l > 0) + { + 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; + } + + if(l == 0) + { + return path; + } + } + + for(unsigned int i = 0; i < i_found; 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) + }; + + calc_intercept_vert(v, pos, path, path_n, normal_last, l); + + if(l == 0) + { + return path; + } + } + + 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]); + } + + return m; +} + diff --git a/src/graphics/mesh/mesh.hpp b/src/graphics/mesh/mesh.hpp new file mode 100644 index 0000000..0f6df3f --- /dev/null +++ b/src/graphics/mesh/mesh.hpp @@ -0,0 +1,47 @@ + +#pragma once + +#include +#include +#include + +#include + +#include "arrays.hpp" + + +namespace sim::graphics +{ + +struct mesh +{ + std::vector vertices; + std::vector indices; + + constexpr mesh() { } + + 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 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; + 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) + { + std::stringstream ss; + ss << header << item; + load_text(ss.str().c_str(), size); + } +}; + +}; + diff --git a/src/graphics/mesh/model.cpp b/src/graphics/mesh/model.cpp new file mode 100644 index 0000000..949944b --- /dev/null +++ b/src/graphics/mesh/model.cpp @@ -0,0 +1,172 @@ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "mesh.hpp" +#include "arrays.hpp" +#include "texture.hpp" + +using namespace sim::graphics; + +struct proc_state +{ + unsigned int offset = 0; + + std::string base; + std::vector vertices; + std::vector indices; + std::unordered_map handles; +}; + +static unsigned int proc_texture(const proc_state& state, aiMaterial* mat, const aiScene* scene) +{ + for(int i = 0; i < 0x0d; i++) + { + 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); + } + + return texture::handle_white; +} + +static void proc_mesh(proc_state& state, glm::mat4 mat, aiMesh* mesh, const aiScene* scene) +{ + 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++) + { + arrays::vertex vertex; + + auto [x, y, z] = mesh->mVertices[i]; + vertex.pos = glm::vec4(x, y, z, 1) * mat; + vertex.texid = handle; + + if(mesh->HasNormals()) + { + auto [x, y, z] = mesh->mNormals[i]; + vertex.normal = glm::vec3(x, y, z) * mat3; + } + + if(mesh->mTextureCoords[0]) + { + auto [x, y, z] = mesh->mTextureCoords[0][i]; + vertex.texpos = {x, y}; + } + + state.vertices.push_back(vertex); + } + + for(unsigned int i = 0; i < mesh->mNumFaces; i++) + { + aiFace face = mesh->mFaces[i]; + + for(unsigned int j = 0; j < face.mNumIndices; j++) + { + state.indices.push_back(face.mIndices[j] + offset); + } + } + + state.offset += mesh->mNumVertices; +} + +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, mat, mesh, scene); + } + + for(size_t i = 0; i < node->mNumChildren; i++) + { + 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}; + std::string path = base + "/" + filename; + Assimp::Importer importer; + + const aiScene *scene = importer.ReadFile(path.c_str(), aiProcess_Triangulate | aiProcess_FlipUVs); + + 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()); + set_indices(&state.indices[0], state.indices.size()); +} + diff --git a/src/graphics/mesh/texture.cpp b/src/graphics/mesh/texture.cpp new file mode 100644 index 0000000..77cd633 --- /dev/null +++ b/src/graphics/mesh/texture.cpp @@ -0,0 +1,100 @@ + +#include +#include +#include + +#include +#include + +#include "texture.hpp" + +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) +{ + if(!data) + { + return 0; + } + + 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); + + 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); + + 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"; + + loaded[path] = handle; + return handle; +} + diff --git a/src/graphics/mesh/texture.hpp b/src/graphics/mesh/texture.hpp new file mode 100644 index 0000000..3313fea --- /dev/null +++ b/src/graphics/mesh/texture.hpp @@ -0,0 +1,17 @@ + +#pragma once + +#include + +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..1cbe0a1 --- /dev/null +++ b/src/graphics/monitor/core.cpp @@ -0,0 +1,219 @@ + +#include +#include + +#include "core.hpp" +#include "helpers.hpp" +#include "../locations.hpp" +#include "../input/focus.hpp" +#include "../mesh/arrays.hpp" +#include "../../system.hpp" + +#include + +#include +#include + +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) + { + sim::system::active.reactor->add_rod_speed(y * 1e-6); + } + + virtual ~core_joystick() + { + sim::system::active.reactor->reset_rod_speed(); + } + + 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; + } +}; + +core::core() +{ +} + +void core::init() +{ + mesh1.model_matrix = locations::monitors[2]; + mesh1.colour_matrix = arrays::colour({1, 1, 1, 1}); + + 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.set(rmesh1, GL_STATIC_DRAW); + rmesh2.load_model("../assets/model/", "reactor_core_interface_cell.stl"); + mesh2.bind(); + mesh2.set(rmesh2, GL_STATIC_DRAW); + + 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_monitor.load_model("../assets/model/", "reactor_core_input.stl"); + m_scram.load_model("../assets/model/", "reactor_core_scram.stl"); +} + +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()) + sys.reactor->scram(); + if(m_buttons[0].check_focus()) + set_all(true); + if(m_buttons[1].check_focus()) + 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->toggle_selected(); + if(m_buttons[5].check_focus()) + sys.reactor->move_cursor(1); + if(m_buttons[6].check_focus()) + sys.reactor->reset_rod_speed(); + if(m_buttons[7].check_focus()) + sys.reactor->move_cursor(sys.reactor->height); +} + +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; + + 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(); + 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(colour[3] == 0) + { + continue; + } + + 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) + { + mesh2.model_matrix = mat * mat_cursor; + mesh2.colour_matrix = arrays::colour({1, 0, 0, 1}); + mesh2.uniform(); + mesh2.render(); + } + + if(r->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 new file mode 100644 index 0000000..e6981f1 --- /dev/null +++ b/src/graphics/monitor/core.hpp @@ -0,0 +1,27 @@ + +#pragma once + +#include "../mesh/glmesh.hpp" + +namespace sim::graphics::monitor +{ + +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; + +public: + + core(); + void init(); + void update(); + void render(); +}; + +}; + diff --git a/src/graphics/monitor/helpers.hpp b/src/graphics/monitor/helpers.hpp new file mode 100644 index 0000000..4e64441 --- /dev/null +++ b/src/graphics/monitor/helpers.hpp @@ -0,0 +1,15 @@ + +#pragma once + +#include + +constexpr double show(double v, double m) +{ + return std::round(v * m) / m; +} + +constexpr double show(double a) +{ + 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 new file mode 100644 index 0000000..961eb8b --- /dev/null +++ b/src/graphics/monitor/primary_loop.cpp @@ -0,0 +1,163 @@ + +#include +#include + +#include "helpers.hpp" +#include "primary_loop.hpp" +#include "../locations.hpp" +#include "../../system.hpp" +#include "../../coolant/valve.hpp" +#include "../input/focus.hpp" + +#include +#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-6); + } + + 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::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]; + 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 Bypass Valve\n\n"; + ss << "Opened\nFlow\n\n"; + ss << "Turbine Inlet Valve\n\n"; + ss << "Opened\nFlow\n\n"; + ss << "Primary Pump\n\n"; + ss << "Power\nSpeed\nFlow\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(); + 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() +{ + std::stringstream ss; + sim::graphics::mesh rmesh; + system& sys = sim::system::active; + + ss << "\n\n"; + ss << show( sys.turbine_bypass_valve->get_state() * 100 ) << " %\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() / 1000 ) << " kg/s\n"; + 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_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"; + ss << show( sys.condenser->get_pressure() / 1000 ) << " kPa\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(); + 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() +{ + mesh1.bind(); + mesh1.uniform(); + mesh1.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 new file mode 100644 index 0000000..eb56eab --- /dev/null +++ b/src/graphics/monitor/primary_loop.hpp @@ -0,0 +1,32 @@ + +#pragma once + +#include "../mesh/glmesh.hpp" + +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: + + primary_loop(); + void init(); + void update(); + void render(); +}; + +}; + diff --git a/src/graphics/monitor/vessel.cpp b/src/graphics/monitor/vessel.cpp new file mode 100644 index 0000000..45daa36 --- /dev/null +++ b/src/graphics/monitor/vessel.cpp @@ -0,0 +1,120 @@ + +#include +#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 + +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(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 << "Reactor Vessel\n\n"; + ss << "Heat\n"; + ss << "Steam\n"; + ss << "Pressure\n"; + ss << "Level\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 << "Control Rods\nMin\nMax\nSpeed\n"; + + rmesh.load_text(ss.str().c_str(), 0.04); + mesh1.bind(); + mesh1.set(rmesh, GL_STATIC_DRAW); +} + +void vessel::update() +{ + sim::system& sys = sim::system::active; + + 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 << 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"; + 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 << 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"; + + rmesh.load_text(ss.str().c_str(), 0.04); + mesh2.bind(); + mesh2.set(rmesh, GL_DYNAMIC_DRAW); +} + +void vessel::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 new file mode 100644 index 0000000..3906568 --- /dev/null +++ b/src/graphics/monitor/vessel.hpp @@ -0,0 +1,22 @@ + +#pragma once + +#include "../mesh/glmesh.hpp" + +namespace sim::graphics::monitor +{ + +class vessel +{ + sim::graphics::glmesh mesh1, mesh2; + +public: + + vessel(); + void init(); + void update(); + void render(); +}; + +}; + diff --git a/src/graphics/resize.cpp b/src/graphics/resize.cpp new file mode 100644 index 0000000..c2cead9 --- /dev/null +++ b/src/graphics/resize.cpp @@ -0,0 +1,65 @@ + +#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; + +glm::vec<2, int> resize::get_size() +{ + return {win_w, win_h}; +} + +float resize::get_aspect() +{ + return (float)win_w / (float)win_h; +} + +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..ae7c3a8 --- /dev/null +++ b/src/graphics/resize.hpp @@ -0,0 +1,15 @@ + +#pragma once + +#include + +namespace sim::graphics::resize +{ + +void init(); +void toggle_fullscreen(); +glm::vec<2, int> get_size(); +float get_aspect(); + +}; + diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp new file mode 100644 index 0000000..974d941 --- /dev/null +++ b/src/graphics/shader.cpp @@ -0,0 +1,81 @@ + +#include +#include + +#include +#include +#include + +#include "shader.hpp" +#include "window.hpp" + +using namespace sim::graphics; + +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) +{ + int id = glCreateShader(type); + + 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(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); + 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; + } + + 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); + 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..b6258ea --- /dev/null +++ b/src/graphics/shader.hpp @@ -0,0 +1,15 @@ + +#pragma once + +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 new file mode 100644 index 0000000..dedef36 --- /dev/null +++ b/src/graphics/window.cpp @@ -0,0 +1,179 @@ + +#include +#include + +#include +#include // glm::translate, glm::rotate, glm::scale +#include // glm::perspective + +#include + +#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 "mesh/font.hpp" +#include "locations.hpp" +#include "monitor/vessel.hpp" +#include "monitor/core.hpp" +#include "monitor/primary_loop.hpp" +#include "mesh/texture.hpp" +#include "ui.hpp" + +using namespace sim::graphics; + +static GLFWwindow* win; +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; + +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) + { + 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_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"; + 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; + } + + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDebugMessageCallback(cb_debug_message, nullptr); + + keyboard::init(); + mouse::init(); + resize::init(); + texture::init(); + font::init(); + ui::init(); + + shader::init_program(); + + sim::system& sys = sim::system::active; + mesh m; + + m.load_model("../assets", "scene-baked.glb"); + MeshScene.bind(); + MeshScene.set(m, 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(); + MonitorPrimaryLoop.init(); + + glfwShowWindow(win); + glViewport(0, 0, 800, 600); +} + +void window::update(double dt) +{ + glfwPollEvents(); + + MonitorCore.update(); + MonitorVessel.update(); + MonitorPrimaryLoop.update(); + + 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]); + projection_matrix = mat_projection; + + glClearColor(0, 0, 0, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + MeshScene.bind(); + MeshScene.uniform(); + MeshScene.render(); + + MonitorCore.render(); + MonitorVessel.render(); + MonitorPrimaryLoop.render(); + + ui::render(); + + glfwSwapBuffers(win); +} + +bool window::should_close() +{ + return win_should_close || glfwWindowShouldClose(win); +} + +void window::close() +{ + win_should_close = true; +} + +void window::destroy() +{ + glfwDestroyWindow(win); + 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..f30ae3e --- /dev/null +++ b/src/graphics/window.hpp @@ -0,0 +1,24 @@ + +#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); +void render(); +void destroy(); +void close(); + +GLFWwindow* get_window(); + +} + diff --git a/src/main.cpp b/src/main.cpp index 507d9dd..3b316f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,23 +1,25 @@ -#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 + +#include +#include +#include +#include + #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 +#include "graphics/mesh/mesh.hpp" +#include "graphics/input/focus.hpp" -const bool do_graphics = true; -const bool do_clock = true; +#include "graphics/window.hpp" +#include "graphics/camera.hpp" + +#include "system.hpp" + +using namespace sim; unsigned long get_now() { @@ -28,189 +30,27 @@ unsigned long get_now() int main() { - std::random_device rd; - std::mt19937 rand(rd()); + feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); - if(do_graphics) - { - initscr(); - cbreak(); - noecho(); - keypad(stdscr, TRUE); - nodelay(stdscr, TRUE); - curs_set(0); - } - - 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#" - }); + graphics::window::create(); - 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(;;) + while(!graphics::window::should_close()) { - 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(dt); - secs += dt; - } + long now = get_now(); + long passed = now - clock; + double dt = (double)passed / 1e6; + clock += passed; - ss << "Vessel\n" << vessel << "\n"; - ss << "Steam Valve\n" << valve << "\n"; - ss << "Coolant Pump\n" << pump << "\n"; + sim::system::active.update(dt); - 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::camera::update(dt); + graphics::window::update(dt); + graphics::focus::update(dt); + graphics::window::render(); } - return 0; + graphics::window::destroy(); } diff --git a/src/reactor/builder.cpp b/src/reactor/builder.cpp new file mode 100644 index 0000000..3f30e58 --- /dev/null +++ b/src/reactor/builder.cpp @@ -0,0 +1,47 @@ + +#include "builder.hpp" + +#include +#include +#include + +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) +{ + std::vector> arr(W * H); + + for(int y = 0; y < H; y++) + for(int x = 0; x < W; x++) + { + char c = lines[y][x]; + std::unique_ptr r; + + switch(c) + { + case 'F': + r = std::make_unique(fr); + break; + case 'C': + r = std::make_unique(v); + break; + case 'G': + r = std::make_unique(); + break; + case 'H': + r = std::make_unique(); + break; + case 'P': + r = std::make_unique(v); + break; + default: + r = std::make_unique(); + break; + } + + arr[y * W + x] = std::move(r); + } + + return reactor(&arr[0], W, H, CW, CH); +} + diff --git a/src/reactor/builder.hpp b/src/reactor/builder.hpp index 16f2c84..1064be0 100644 --- a/src/reactor/builder.hpp +++ b/src/reactor/builder.hpp @@ -3,52 +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 { -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, 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 new file mode 100644 index 0000000..1830fc8 --- /dev/null +++ b/src/reactor/control/boron_rod.cpp @@ -0,0 +1,58 @@ + +#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) : coolant::pipe(v) +{ + +} + +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 m = (1 - absorbed) * inserted; + double r_fast = vals[val_t::N_FAST] * m; + + vals[val_t::N_FAST] -= r_fast; + absorbed += r_fast / 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 53% rename from src/reactor/control/control_rod.hpp rename to src/reactor/control/boron_rod.hpp index 806a47d..7652cf0 100644 --- a/src/reactor/control/control_rod.hpp +++ b/src/reactor/control/boron_rod.hpp @@ -6,28 +6,28 @@ namespace sim::reactor::control { -class control_rod : public sim::reactor::coolant::pipe +class boron_rod : public 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 const char* get_name() const { return "Boron Control Rod"; } + 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); 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; } virtual void update_selected(double a); + virtual std::unique_ptr clone() const { return std::make_unique(*this); } }; } 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 202381c..f5b8316 100644 --- a/src/reactor/control/graphite_rod.hpp +++ b/src/reactor/control/graphite_rod.hpp @@ -11,9 +11,12 @@ 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 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: @@ -23,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 05cab44..75958f0 100644 --- a/src/reactor/coolant/heater.hpp +++ b/src/reactor/coolant/heater.hpp @@ -12,8 +12,10 @@ 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 int get_id() const { return 3; } public: @@ -21,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.cpp b/src/reactor/coolant/pipe.cpp index e78810e..bc06964 100644 --- a/src/reactor/coolant/pipe.cpp +++ b/src/reactor/coolant/pipe.cpp @@ -1,17 +1,18 @@ #include "pipe.hpp" +#include "../reactor.hpp" using namespace sim::reactor::coolant; -pipe::pipe(coolant::vessel& v) +pipe::pipe(coolant::vessel* v) { - this->vessel = &v; + this->vessel = v; this->steam = 0; } 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) @@ -22,8 +23,11 @@ void pipe::update(double secs) void pipe::update_pipe(double secs) { - vals[val_t::HEAT] = vessel->add_heat(vals[val_t::HEAT]); - vals[val_t::N_SLOW] += vals[val_t::N_FAST]; + sim::reactor::reactor* r = (sim::reactor::reactor*)reactor; + double m_heat = r->cell_width * r->cell_width * r->cell_height * 1e6; + + 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/coolant/pipe.hpp b/src/reactor/coolant/pipe.hpp index 1e877bd..001e9c1 100644 --- a/src/reactor/coolant/pipe.hpp +++ b/src/reactor/coolant/pipe.hpp @@ -16,12 +16,15 @@ protected: virtual double get_k(sim::reactor::rod::val_t type) const; virtual const char* get_name() const { return "Coolant"; } + virtual int get_id() const { return 2; } + void update_pipe(double secs); 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; } virtual void update(double secs); diff --git a/src/reactor/coolant/vessel.cpp b/src/reactor/coolant/vessel.cpp index fa2bbf0..a3dcc96 100644 --- a/src/reactor/coolant/vessel.cpp +++ b/src/reactor/coolant/vessel.cpp @@ -1,9 +1,10 @@ #include "vessel.hpp" -#include "../../constants.hpp" +#include "../../util/constants.hpp" #include "../../conversions/temperature.hpp" #include "../fuel/half_life.hpp" +#include #include using namespace sim::reactor::coolant; @@ -15,127 +16,65 @@ 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 mass, double level) : + sim::coolant::fluid_holder(fluid, calc_cylinder(height, diameter), mass), + height(height), diameter(diameter) { + this->level = level; +} + +double vessel::get_steam_suspended() const +{ + return steam_suspended; +} + +double vessel::get_void_ratio() const +{ + double density = get_steam_density(); + + if(density == 0) + { + return 0; + } + + double s = steam_suspended / density; + double m = level + s; + if(m == 0) + { + return 0; + } + + return s / m; +} + +double vessel::get_bubble_hl() const +{ + 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; - double s = steam + n; - double l = fluid.l_to_g(level) - n; - double v = fluid.l_to_g(volume); + ((sim::coolant::fluid_holder*)this)->update(secs); - if(l < 0) + double diff = steam - s; + double hl = get_bubble_hl(); + + if(diff > 0) { - s += l; - l = 0; + steam_suspended += diff; } - if(s > v) + if(hl > 0) { - s = v; - l = 0; + steam_suspended *= reactor::fuel::half_life::get(secs, get_bubble_hl()); } - if(l > v) + else { - l = v; - s = 0; + steam_suspended = 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 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; -} - -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; -} - -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..0eafa1a 100644 --- a/src/reactor/coolant/vessel.hpp +++ b/src/reactor/coolant/vessel.hpp @@ -3,47 +3,40 @@ #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; // celcius - 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 + double steam_suspended = 0; // grams - const sim::coolant::fluid_t fluid; + vessel(sim::coolant::fluid_t fluid, double height, double diameter, double mass, double level); - vessel(double height, double diameter, double level, sim::coolant::fluid_t fluid); + double get_steam_suspended() const; // grams + double get_void_ratio() const; + double get_bubble_hl() const; void update(double secs); - double add_heat(double amount); - 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); } + 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"; - double get_pressure() const; // pascals + return o; + } + }; -} - -std::ostream& operator<<(std::ostream& o, const sim::reactor::coolant::vessel& v); +}; diff --git a/src/reactor/fuel/fuel_rod.cpp b/src/reactor/fuel/fuel_rod.cpp index fc96c2d..fe2c250 100644 --- a/src/reactor/fuel/fuel_rod.cpp +++ b/src/reactor/fuel/fuel_rod.cpp @@ -1,33 +1,95 @@ #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: " << 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; +} + +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.clear_slow_neutrons(); + 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::N_SLOW] = 0; + 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] = s.get_slow_neutrons() * mol; } diff --git a/src/reactor/fuel/fuel_rod.hpp b/src/reactor/fuel/fuel_rod.hpp index 892cd55..ff4be62 100644 --- a/src/reactor/fuel/fuel_rod.hpp +++ b/src/reactor/fuel/fuel_rod.hpp @@ -14,14 +14,18 @@ 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 int get_id() const { return 1; } + virtual double get_energy_output() const; + virtual glm::vec4 get_colour() const; public: - fuel_rod(double fuel, double mass); + 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/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 8ded7d5..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-10; +constexpr double NEUTRON_BG = 1e-30; -sample::sample(double fuel, double mass) +sample::sample(double fuel) { this->fuel = fuel; - this->mass = mass; + 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(); + 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); @@ -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..777479c 100644 --- a/src/reactor/fuel/sample.hpp +++ b/src/reactor/fuel/sample.hpp @@ -19,9 +19,10 @@ 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/s + double energy = 0; // W double fast_neutrons = 0; // mol/s double slow_neutrons = 0; // mol/s double efficiency = 0; @@ -31,7 +32,7 @@ class sample public: - sample(double fuel, double mass); + sample(double fuel); void update(double secs); @@ -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 new file mode 100644 index 0000000..db5448c --- /dev/null +++ b/src/reactor/reactor.cpp @@ -0,0 +1,241 @@ + +#include "reactor.hpp" +#include "../util/random.hpp" + +#include + +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++) + { + this->rods[i] = std::move(rods[i]); + this->rods[i]->reactor = this; + } +} + +reactor::reactor(reactor&& o) : cell_width(o.cell_width), cell_height(o.cell_height), width(o.width), height(o.height), size(o.size) +{ + 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) +{ + rods = std::vector>(width * height); + cursor = o.cursor; + + for(int i = 0; i < size; i++) + { + rods[i] = std::unique_ptr(o.rods[i]->clone()); + rods[i]->reactor = this; + } +} + +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++) + { + rods_lookup[i] = i; + } + + for(int i = 0; i < size; i++) + { + rods[i]->update(secs); + } + + update_interactions(rods_lookup, secs / 2); + + if(std::abs(rod_speed) < 1e-6) + { + rod_speed = 0; + } + + if(rod_speed != 0) + { + update_selected(secs); + } +} + +void reactor::update_selected(double dt) +{ + for(int i = 0; i < size; i++) + { + rod* r = rods[i].get(); + + if(r->selected) + { + r->update_selected(rod_speed * dt); + } + } +} + +int reactor::move_cursor(int d) +{ + goto logic; + + while(cursor != size && !rods[cursor]->should_display()) + { +logic: cursor = (cursor + d) % (size + 1); + + if(cursor < 0) + { + cursor += size + 1; + } + + if(d > 1) d = 1; + if(d < -1) d = -1; + } + + return cursor; +} + +void reactor::toggle_selected() +{ + if(cursor < size && rods[cursor]->should_select()) + { + rods[cursor]->toggle_selected(); + } +} + +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], sim::random::gen); + + 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(int* rods_lookup, double secs) +{ + std::shuffle(rods_lookup, &rods_lookup[size - 1], sim::random::gen); + + 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(secs, i, x, y); + } + } +} + +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; +} + +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; + 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 856fba4..f3d80e6 100644 --- a/src/reactor/reactor.hpp +++ b/src/reactor/reactor.hpp @@ -3,126 +3,48 @@ #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; + const double cell_width; + const double cell_height; - rod* rods[size]; - - 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(); - } - } + const int width; + const int height; + const int size; + + std::vector> rods; + 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 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(); + 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(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 75b02c5..cc30173 100644 --- a/src/reactor/rod.cpp +++ b/src/reactor/rod.cpp @@ -1,10 +1,14 @@ #include "rod.hpp" +#include "reactor.hpp" #include using namespace sim::reactor; +// Avogadro's Number +static double N_a = 6.02214076e23; + double rod::get(val_t type) const { return vals[type]; @@ -28,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; } @@ -37,15 +41,31 @@ 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_speed() const +double rod::get_flux() const { - int m = motion < 0 ? -1 : 1; - return motion == 0 ? 0 : (std::pow(10, std::abs(motion)) * 1e-6 * m); + 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) @@ -55,22 +75,10 @@ void rod::update_rod(double secs) vals[val_t::N_FAST] *= m; vals[val_t::N_SLOW] *= m; - if(motion != 0 && !is_selected()) + // clear data + for(int i = 0; i < rod::VAL_N; i++) { - motion = 0; - } - - if(motion != 0) - { - update_selected(get_speed() * secs); + vals_n[(val_t)i] = 0; } } -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 eac3b50..32e27fe 100644 --- a/src/reactor/rod.hpp +++ b/src/reactor/rod.hpp @@ -2,6 +2,8 @@ #pragma once #include +#include +#include namespace sim::reactor { @@ -9,28 +11,39 @@ namespace sim::reactor class rod { public: - - static const int VAL_N = 3; + + bool selected = false; + void* reactor = nullptr; + static const int VAL_N = 4; enum val_t { HEAT = 0, N_SLOW = 1, - N_FAST = 2 + N_FAST = 2, }; + virtual ~rod() {}; 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 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; } - void update_rod_selected(int m); + 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) { @@ -38,11 +51,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"; @@ -55,16 +63,13 @@ public: protected: double vals[VAL_N] = {0}; - bool selected = false; - int motion = 0; + double vals_n[VAL_N] = {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/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 + diff --git a/src/system.cpp b/src/system.cpp new file mode 100644 index 0000000..0805a3f --- /dev/null +++ b/src/system.cpp @@ -0,0 +1,73 @@ + +#include "system.hpp" + +#include "reactor/builder.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 system::active; + +system::system() +{ + const char* layout[] = { + " 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 " + }; + + 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, 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, 1e6, 1, 10); +} + +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); + primary_pump = std::move(o.primary_pump); +} + +void system::update(double dt) +{ + dt *= speed; + + reactor->update(dt); + vessel->update(dt); + + turbine_inlet_valve->update(dt); + turbine_bypass_valve->update(dt); + + turbine->update(dt); + condenser->update(dt); + primary_pump->update(dt); +} + diff --git a/src/system.hpp b/src/system.hpp new file mode 100644 index 0000000..f2aa8ba --- /dev/null +++ b/src/system.hpp @@ -0,0 +1,39 @@ + +#pragma once + +#include + +#include "reactor/coolant/vessel.hpp" +#include "reactor/reactor.hpp" +#include "coolant/pump.hpp" +#include "coolant/valve.hpp" +#include "coolant/condenser.hpp" +#include "electric/turbine.hpp" +#include "graphics/mesh/mesh.hpp" + +namespace sim +{ + +struct system +{ + static system active; + + std::unique_ptr reactor; + std::unique_ptr vessel; + std::unique_ptr condenser; + std::unique_ptr turbine; + std::unique_ptr primary_pump; + std::unique_ptr turbine_bypass_valve; + std::unique_ptr turbine_inlet_valve; + sim::graphics::mesh scene; + double speed = 1; + + system(); + system(system&& o); + system(const system& o) = delete; + + void update(double dt); +}; + +}; + 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/math.hpp b/src/util/math.hpp new file mode 100644 index 0000000..53cb189 --- /dev/null +++ b/src/util/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; +} + 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/util/random.cpp b/src/util/random.cpp new file mode 100644 index 0000000..f57f5e3 --- /dev/null +++ b/src/util/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/util/random.hpp b/src/util/random.hpp new file mode 100644 index 0000000..0b97b0a --- /dev/null +++ b/src/util/random.hpp @@ -0,0 +1,14 @@ + +#pragma once + +#include + +namespace sim::random +{ + +extern std::mt19937 gen; + +void init(); + +}; +