summaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
authorJon Santmyer <jon@jonsantmyer.com>2022-06-22 17:41:59 -0400
committerJon Santmyer <jon@jonsantmyer.com>2022-06-22 17:41:59 -0400
commit5e3a2492c7bb73daa4e27398daaf490d09980ff3 (patch)
tree75178d823d596b6a898002c3f1d45b9ceede0e1e /include
downloadsystemviewer-5e3a2492c7bb73daa4e27398daaf490d09980ff3.tar.gz
systemviewer-5e3a2492c7bb73daa4e27398daaf490d09980ff3.tar.bz2
systemviewer-5e3a2492c7bb73daa4e27398daaf490d09980ff3.zip
Base system viewer with data loaded from csv files
Diffstat (limited to 'include')
-rw-r--r--include/camera.hpp96
-rw-r--r--include/csv.hpp115
-rw-r--r--include/ecs.hpp139
-rw-r--r--include/entitycomponents.hpp58
-rw-r--r--include/game.hpp52
-rw-r--r--include/input.hpp71
-rw-r--r--include/keybind.hpp71
-rw-r--r--include/shape.hpp197
-rw-r--r--include/straw.hpp312
-rw-r--r--include/system.hpp82
-rw-r--r--include/timeman.hpp27
-rw-r--r--include/units.hpp166
-rw-r--r--include/util.hpp45
-rw-r--r--include/vex.hpp104
-rw-r--r--include/window.hpp69
15 files changed, 1604 insertions, 0 deletions
diff --git a/include/camera.hpp b/include/camera.hpp
new file mode 100644
index 0000000..9a8bc00
--- /dev/null
+++ b/include/camera.hpp
@@ -0,0 +1,96 @@
+#ifndef CAMERA_HPP
+#define CAMERA_HPP 1
+
+#include "vex.hpp"
+#include "shape.hpp"
+#include "straw.hpp"
+
+#include <vector>
+#include <concepts>
+#include <algorithm>
+
+class Camera;
+struct RenderBatchEntry {
+ shapes::shapes_variant<long> shape;
+ straw::color fg, bg;
+ char c;
+
+ template<class T>
+ constexpr RenderBatchEntry(T Shape) : shape(Shape), fg(straw::WHITE), bg(straw::BLACK), c('#') {}
+ template<class T>
+ constexpr RenderBatchEntry(T Shape, char C) : shape(Shape), fg(straw::WHITE), bg(straw::BLACK), c(C) {}
+ template<class T>
+ constexpr RenderBatchEntry(T Shape, straw::color Fg, char C) : shape(Shape), fg(Fg), bg(straw::BLACK), c(C) {}
+ template<class T>
+ constexpr RenderBatchEntry(T Shape, straw::color Fg, straw::color Bg, char C) : shape(Shape), fg(Fg), bg(Bg), c(C) {}
+
+ void translate(Camera *camera);
+ void plot(Camera *camera);
+private:
+ void plotPoint(shapes::point<long> point, Camera *camera);
+ void plotLine(shapes::line<long> line, Camera *camera);
+ void plotRectangle(shapes::rectangle<long> rectangle, Camera *camera);
+ void plotCircle(shapes::circle<long> circle, Camera *camera);
+ void plotEllipse(shapes::ellipse<long> ellipse, Camera *camera);
+};
+
+class Camera
+{
+ friend struct RenderBatchEntry;
+
+ vex::vec2<long> m_position;
+ vex::vec2<long> m_origin;
+ shapes::rectangle<long> m_frustum;
+
+ std::vector<RenderBatchEntry> m_shapeBatch;
+
+ long m_scale;
+ bool m_dirty;
+
+ straw::screen<char> *m_viewport;
+
+ void updateFrustum();
+
+ template<class T>
+ void translateShape(T &shape);
+public:
+ Camera(straw::screen<char> *viewport) :
+ m_position(0, 0),
+ m_origin(0, 0),
+ m_frustum(0, 0, 0, 0),
+ m_scale(16384),
+ m_dirty(true),
+ m_viewport(viewport) {}
+ ~Camera() {}
+
+ constexpr long getscale() const { return m_scale; }
+ constexpr vex::vec2<long> getpos() const { return m_position; }
+ constexpr vex::vec2<long> getorigin() const { return m_origin; }
+
+ constexpr bool dirty() const { return m_dirty; }
+ void markDirty() { m_dirty = true; }
+
+ void zoom(long by) { m_scale = std::max((unsigned)((int)m_scale + by), (unsigned)1); m_dirty = true; }
+ void setscale(long scale) { m_scale = std::max(scale, (long)1); m_dirty = true; }
+
+ void move(const vex::vec2<long> &pos) { m_position += pos; m_dirty = true; }
+ void move(long x, long y) { move(vex::vec2<long>(x, y)); }
+
+ void setpos(const vex::vec2<long> &pos) { m_position = pos; m_dirty = true; }
+ void setpos(long x, long y) { setpos(vex::vec2<long>(x, y)); }
+
+ void setorigin(const vex::vec2<long> &origin) { m_origin = origin; m_dirty = true; }
+
+ template<class T>
+ void batchShape(T shape) { m_shapeBatch.emplace_back(shape); }
+ template<class T>
+ void batchShape(T shape, char c) { m_shapeBatch.emplace_back(shape, c); }
+ template<class T>
+ void batchShape(T shape, straw::color fg, char c) { m_shapeBatch.emplace_back(shape, fg, c); }
+ template<class T>
+ void batchShape(T shape, straw::color fg, straw::color bg, char c) { m_shapeBatch.emplace_back(shape, fg, bg, c); }
+
+ void draw();
+};
+
+#endif
diff --git a/include/csv.hpp b/include/csv.hpp
new file mode 100644
index 0000000..18e30b0
--- /dev/null
+++ b/include/csv.hpp
@@ -0,0 +1,115 @@
+#ifndef CSV_HPP
+#define CSV_HPP 1
+
+#include <tuple>
+#include <string>
+#include <vector>
+#include <sstream>
+#include <fstream>
+#include <iostream>
+
+namespace csv {
+
+template<char T>
+struct csv_delim : std::ctype<char> {
+ csv_delim() : std::ctype<char>(get_table()) {}
+ static mask const *get_table() {
+ static mask rc[table_size];
+ rc[T] = std::ctype_base::space;
+ rc['\n'] = std::ctype_base::space;
+ return &rc[0];
+ }
+};
+
+template<char Seperator, typename... Vals>
+class CSVFile {
+public:
+ struct CSVLine {
+ std::tuple<Vals...> data;
+
+ CSVLine(const std::string &line) {
+ std::istringstream in(line);
+ in.imbue(std::locale(std::locale(), new csv_delim<Seperator>));
+ read_elements(in, std::make_index_sequence<sizeof...(Vals)>{});
+ }
+
+ template<std::size_t... I>
+ void read_elements(std::istream &in, std::index_sequence<I...>) {
+ std::initializer_list<bool>{read_element(in, std::get<I>(data))...};
+ }
+
+ template<typename T>
+ bool read_element(std::istream &in, T &value) {
+ in >> value; return true;
+ }
+ };
+ struct Reader {
+ private:
+ std::vector<CSVLine> m_lines;
+ public:
+ Reader() {}
+ Reader(CSVFile *parent) {
+ for(std::string line; std::getline(parent->m_rfile, line);) {
+ m_lines.emplace_back(line);
+ }
+ }
+ std::vector<std::tuple<Vals...>> get() const {
+ std::vector<std::tuple<Vals...>> r;
+ for(auto &line : m_lines) r.push_back(line.data);
+ return r;
+ }
+
+ };
+ struct Writer {
+ private:
+ std::vector<std::tuple<Vals...>> m_data;
+ public:
+ void put(std::tuple<Vals...> line) { m_data.push_back(line); }
+ void write(CSVFile *parent) {
+ for(auto &line : m_data) {
+ write_line(parent, line, std::make_index_sequence<sizeof...(Vals)>{});
+ parent->m_wfile.seekp(-1, std::ios_base::end);
+ parent->m_wfile << "\n";
+ }
+ }
+ template<std::size_t... I>
+ void write_line(CSVFile *parent, std::tuple<Vals...> line, std::index_sequence<I...>) {
+ std::initializer_list<bool>{write_element(parent, std::get<I>(line))...};
+ }
+ template<typename T>
+ bool write_element(CSVFile *parent, T &value) {
+ parent->m_wfile << value << Seperator; return true;
+ }
+ };
+
+ friend struct Reader;
+ friend struct Writer;
+
+ CSVFile(const std::string &path, bool write = false) {
+ //std::ios_base::iostate emask = m_file.exceptions() | std::ios::failbit;
+ if(!write) {
+ m_rfile.open(path);
+ m_reader = Reader(this);
+ }else{
+ m_wfile.open(path);
+ }
+ }
+ ~CSVFile() { m_rfile.close(); m_wfile.close(); }
+
+ const std::tuple<Vals...> operator[](std::size_t i) { return m_reader.m_lines[i].data; }
+ std::size_t size() const { return m_reader.m_lines.size(); }
+
+ std::vector<std::tuple<Vals...>> get() const { return m_reader.get(); }
+ void put(std::tuple<Vals...> vals) { m_writer.put(vals); }
+ void write() { m_writer.write(this); }
+
+protected:
+ std::ifstream m_rfile;
+ std::ofstream m_wfile;
+ Reader m_reader;
+ Writer m_writer;
+};
+
+}
+
+#endif
diff --git a/include/ecs.hpp b/include/ecs.hpp
new file mode 100644
index 0000000..ab5e2b2
--- /dev/null
+++ b/include/ecs.hpp
@@ -0,0 +1,139 @@
+#ifndef ECS_HPP
+#define ECS_HPP 1
+
+#include "entitycomponents.hpp"
+#include "util.hpp"
+#include <bitset>
+#include <typeindex>
+#include <queue>
+#include <cassert>
+#include <ranges>
+#include <span>
+#include <iostream>
+
+namespace ecs {
+
+struct ComponentContainer {
+ std::vector<component_variant> packed{};
+ std::vector<int> sparse{};
+ std::queue<unsigned> free{};
+
+ void resize(size_t n) { sparse.resize(n, -1); }
+
+ template<component_type T>
+ void insert(unsigned id, const T &c) {
+ assert(sparse.size() > id);
+ if(free.empty()) {
+ sparse[id] = (int)packed.size();
+ packed.push_back(c);
+ }else {
+ sparse[id] = (int)free.front();
+ packed[sparse[id]] = c;
+ free.pop();
+ }
+ }
+
+ void remove(unsigned id) {
+ assert(sparse.size() > id);
+ if(sparse[id] > -1) {
+ free.push((unsigned)sparse[id]);
+ }
+ sparse[id] = -1;
+ }
+
+ template<component_type T>
+ T &reduce(unsigned id) {
+ assert(sparse.size() > id);
+ assert(sparse[id] > -1);
+ return std::get<T>(packed[sparse[id]]);
+ }
+};
+
+class EntityMan;
+struct Entity {
+ EntityMan *man;
+ component_sig sig{};
+ unsigned id;
+
+ Entity(const Entity &e) = default;
+ Entity(EntityMan *Man, unsigned Id) :
+ man(Man), sig(0), id(Id) {}
+
+
+ template<component_type T>
+ constexpr bool contains() { return sig.test(get_index<T, component_variant>()); }
+
+ template<component_type T>
+ T &get();
+
+ template<component_type T>
+ Entity &addComponent(const T &component);
+};
+
+class EntityMan {
+ std::array<ComponentContainer, std::variant_size_v<component_variant>> m_components;
+ std::vector<Entity> m_entities;
+ std::queue<unsigned> m_free;
+public:
+ Entity &operator[](std::size_t i) { return m_entities[i]; }
+
+ Entity newEntity() {
+ std::size_t newid = 0;
+ if(!m_free.empty()) {
+ newid = m_free.front();
+ m_free.pop();
+ }else{
+ newid = m_entities.size();
+ m_entities.emplace_back(this, newid);
+ for(auto &container : m_components) container.resize(m_entities.size());
+ }
+ return m_entities[newid];
+ }
+
+ void deleteEntity(Entity &e) {
+ for(auto &container : m_components) container.remove(e.id);
+ e.sig.reset();
+ m_free.push(e.id);
+ }
+
+ template<component_type T>
+ T &get(const Entity &e) {
+ constexpr unsigned cid = get_index<T, component_variant>();
+ return m_components[cid].reduce<T>(e.id);
+ }
+
+ template<component_type T>
+ auto getWith() {
+ constexpr unsigned cid = get_index<T, component_variant>();
+ auto e_hc = [](Entity const e) { return e.sig[cid]; };
+ return std::ranges::views::filter(m_entities, e_hc);
+ }
+
+ std::span<Entity> all() {
+ return std::span<Entity>(m_entities.begin(), m_entities.end());
+ }
+
+ std::size_t size() { return m_entities.size(); }
+
+ template<component_type T>
+ Entity &addComponent(Entity &entity, const T& component) {
+ constexpr unsigned cid = get_index<T, component_variant>();
+ m_components[cid].insert(entity.id, component);
+ m_entities[entity.id].sig[cid] = 1;
+ return entity;
+ }
+};
+
+template<component_type T>
+T &Entity::get() {
+ return man->get<T>(*this);
+}
+
+template<component_type T>
+Entity &Entity::addComponent(const T& component) {
+ return man->addComponent(*this, component);
+}
+
+}
+
+#endif
diff --git a/include/entitycomponents.hpp b/include/entitycomponents.hpp
new file mode 100644
index 0000000..a263ebf
--- /dev/null
+++ b/include/entitycomponents.hpp
@@ -0,0 +1,58 @@
+#ifndef ENTITY_COMPONENT_HPP
+#define ENTITY_COMPONENT_HPP 1
+
+#include "vex.hpp"
+#include "units.hpp"
+#include <bitset>
+
+namespace ecs {
+struct PositionComponent {
+ vex::vec2<long> position{};
+};
+
+struct VelocityComponent {
+ vex::vec2<long> velocity{};
+};
+
+struct MassComponent {
+ unit::Mass mass;
+};
+
+struct NameComponent {
+ std::string name;
+};
+
+struct OrbitalComponent {
+ unsigned origin;
+ long a;
+ double e;
+ double w;
+ double M;
+ double T;
+ double v;
+};
+
+struct RenderCircleComponent {
+ unsigned radius;
+};
+
+using component_variant = std::variant<
+ std::monostate,
+ PositionComponent,
+ VelocityComponent,
+ MassComponent,
+ NameComponent,
+ OrbitalComponent,
+ RenderCircleComponent
+>;
+
+using component_sig =
+ std::bitset<std::variant_size_v<component_variant>>;
+
+template<typename T>
+concept component_type =
+ is_variant_v<T, component_variant>;
+
+}
+
+#endif
diff --git a/include/game.hpp b/include/game.hpp
new file mode 100644
index 0000000..9454318
--- /dev/null
+++ b/include/game.hpp
@@ -0,0 +1,52 @@
+#ifndef GAME_HPP
+#define GAME_HPP 1
+
+#include "window.hpp"
+#include "camera.hpp"
+#include "ecs.hpp"
+#include "system.hpp"
+#include "timeman.hpp"
+#include "input.hpp"
+
+#include <memory>
+
+#define WINCTX_GAME "Game"
+
+class Game
+{
+public:
+ enum class State {
+ STOPPED, RUNNING, RUNNING_INPUT, PAUSED, PAUSED_INPUT
+ };
+
+ static void setup(unsigned w, unsigned h);
+ static void cleanup();
+
+ static void turn();
+ static void setState(State state) { m_state = state; }
+ static void setContext(const std::string &id) { m_currentContext = id; }
+
+ static bool running() { return m_state != State::STOPPED; }
+ static bool paused() { return m_state == State::PAUSED || m_state == State::PAUSED_INPUT; }
+ static bool inputMode() { return m_state == State::RUNNING_INPUT || m_state == State::PAUSED_INPUT; }
+
+ struct WindowContexts {
+ WindowContext &operator[](const std::string &id) { return Game::m_contexts.at(id); }
+ WindowContext &operator()() { return Game::m_contexts.at(Game::m_currentContext); }
+ };
+ static WindowContexts contexts;
+private:
+ static std::unordered_map<std::string, WindowContext> m_contexts;
+ static std::string m_currentContext;
+
+ static std::unique_ptr<Camera> m_camera;
+ static std::unique_ptr<System> m_system;
+ static SystemView m_systemView;
+
+ static input::Context m_inputContext;
+
+ static double m_delta;
+ static State m_state;
+};
+
+#endif
diff --git a/include/input.hpp b/include/input.hpp
new file mode 100644
index 0000000..ed5155c
--- /dev/null
+++ b/include/input.hpp
@@ -0,0 +1,71 @@
+#ifndef INPUT_HPP
+#define INPUT_HPP 1
+
+namespace input
+{
+
+enum extkeys {
+ CTRL_RANGE_START = 256,
+ CTRL_KEY_ARROWUP = 321,
+ CTRL_KEY_ARROWDOWN,
+ CTRL_KEY_ARROWRIGHT,
+ CTRL_KEY_ARROWLEFT,
+ CTRL_KEY_END = 326,
+ CTRL_KEY_HOME = 328,
+ CTRL_KEY_PAGEUP,
+ CTRL_KEY_PAGEDOWN
+};
+
+#ifdef __unix__
+#include <termios.h>
+#include <unistd.h>
+
+class Context
+{
+ struct termios m_termold;
+ struct termios m_termnow;
+public:
+ Context() {
+ tcgetattr(STDIN_FILENO, &m_termold);
+ m_termnow = m_termold;
+ }
+ ~Context() {
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &m_termold);
+ }
+
+ void echo(bool mode) {
+ if(mode){
+ m_termnow.c_lflag |= ECHO;
+ }else{
+ m_termnow.c_lflag &= ~ECHO;
+ }
+ tcsetattr(STDIN_FILENO, TCSANOW, &m_termnow);
+ }
+
+ void canon(bool mode) {
+ if(mode) {
+ m_termnow.c_lflag |= ICANON;
+ }else{
+ m_termnow.c_lflag &= ~ICANON;
+ }
+ tcsetattr(STDIN_FILENO, TCSANOW, &m_termnow);
+ }
+
+ void cbreak(bool mode) {
+ if(mode){
+ m_termnow.c_cc[VMIN] = m_termold.c_cc[VMIN];
+ m_termnow.c_cc[VTIME] = m_termold.c_cc[VTIME];
+ }else{
+ m_termnow.c_cc[VMIN] = 1;
+ m_termnow.c_cc[VTIME] = 0;
+ }
+ tcsetattr(STDIN_FILENO, TCSANOW, &m_termnow);
+ }
+};
+
+#endif
+
+extern int getcode();
+}
+
+#endif
diff --git a/include/keybind.hpp b/include/keybind.hpp
new file mode 100644
index 0000000..6541a21
--- /dev/null
+++ b/include/keybind.hpp
@@ -0,0 +1,71 @@
+#ifndef KEYBIND_HPP
+#define KEYBIND_HPP 1
+
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <unordered_map>
+
+#define CTX_GLOBAL "Global"
+#define CTX_SYSTEMVIEW "System View"
+#define CTX_TIMEMAN "Time Manager"
+
+#define BIND_G_QUIT "G_Quit"
+#define BIND_G_NEXTWIN "G_NextWindow"
+#define BIND_G_PREVWIN "G_PrevWindow"
+#define BIND_G_EDITBINDS "G_EditBinds"
+#define BIND_G_ESCAPE "G_Escape"
+#define BIND_G_SELECT "G_Select"
+
+#define BIND_SYSTEMVIEW_PANUP "Systemview_PanUp"
+#define BIND_SYSTEMVIEW_PANDOWN "Systemview_PanDown"
+#define BIND_SYSTEMVIEW_PANLEFT "Systemview_PanLeft"
+#define BIND_SYSTEMVIEW_PANRIGHT "Systemview_PanRight"
+#define BIND_SYSTEMVIEW_DECSCALE "Systemview_DecScale"
+#define BIND_SYSTEMVIEW_INCSCALE "Systemview_IncScale"
+#define BIND_SYSTEMVIEW_SEARCH "Systemview_Search"
+
+#define BIND_SYSTEMVIEW_SEARCH_PREV "Systemview_Search_Prev"
+#define BIND_SYSTEMVIEW_SEARCH_NEXT "Systemview_Search_Next"
+#define BIND_SYSTEMVIEW_SEARCH_TOP "Systemview_Search_Top"
+#define BIND_SYSTEMVIEW_SEARCH_BOTTOM "Systemview_Search_Bottom"
+#define BIND_SYSTEMVIEW_SEARCH_COLLAPSE "Systemview_Search_Collapse"
+
+#define BIND_TIMEMAN_STEP "Timeman_Step"
+#define BIND_TIMEMAN_INCSTEP "Timeman_IncStep"
+#define BIND_TIMEMAN_DECSTEP "Timeman_DecStep"
+#define BIND_TIMEMAN_TOGGLEAUTO "Timeman_ToggleAuto"
+
+class KeyMan {
+public:
+ struct Bind {
+ int code;
+ std::string name;
+ std::string ctx;
+ std::string desc;
+ };
+
+ struct Binds {
+ std::vector<Bind> operator()() {
+ std::vector<Bind> bindList;
+ std::transform(
+ KeyMan::m_keybinds.begin(),
+ KeyMan::m_keybinds.end(),
+ std::back_inserter(bindList), [](auto &pair){return pair.second;});
+ return bindList;
+ }
+ Bind &operator[](const std::string &name) { return KeyMan::m_keybinds[name]; }
+ };
+ static Binds binds;
+
+ static void registerBind(int def, const std::string &name, const std::string &context, const std::string &desc);
+ static void loadKeybindsFrom(const std::string &csvPath);
+ static void writeKeybindsTo(const std::string &csvPath);
+ static std::string translateCode(int code);
+
+private:
+ static std::unordered_map<std::string, Bind> m_keybinds;
+ static std::unordered_map<std::string, std::string> m_keybindContexts;
+};
+
+#endif
diff --git a/include/shape.hpp b/include/shape.hpp
new file mode 100644
index 0000000..0d9db96
--- /dev/null
+++ b/include/shape.hpp
@@ -0,0 +1,197 @@
+#ifndef SHAPE_HPP
+#define SHAPE_HPP 1
+
+#include "vex.hpp"
+#include <iostream>
+#include <variant>
+
+namespace shapes {
+
+template<vex::arithmetic T>
+struct shape
+{
+ vex::vec2<T> position;
+
+ constexpr shape(const vex::vec2<T> p) : position(p) {}
+ constexpr shape(T x, T y) : position(x, y) {}
+
+ virtual void scale(T by) = 0;
+ virtual void translate(vex::vec2<T> by) { position += by; }
+};
+
+template<vex::arithmetic T>
+struct point : public shape<T>
+{
+ constexpr point(T x, T y) : shape<T>(vex::vec2<T>(x, y)) {}
+ constexpr point(const vex::vec2<T> &pos) : shape<T>(pos) {}
+
+ void scale(T by) override { (void)by; }
+};
+
+template<vex::arithmetic T>
+struct line : public shape<T>
+{
+ vex::vec2<T> end;
+ constexpr line(T x, T y, T z, T w) : shape<T>(vex::vec2<T>(x, y)), end(z, w){}
+ constexpr line(const vex::vec2<T> &pos, const vex::vec2<T> End) : shape<T>(pos), end(End) {}
+
+ void translate(vex::vec2<T> by)
+ {
+ this->position += by;
+ end += by;
+ }
+ void scale(T by) override {
+ end = vex::vec2<T>(
+ (T)std::floor((double)end[0] / (double)by),
+ (T)std::floor((double)end[1] / (double)by));
+ }
+};
+
+template<vex::arithmetic T>
+struct circle : public shape<T>
+{
+ T radius;
+ constexpr circle(T x, T y, T r) : shape<T>(vex::vec2<T>(x, y)), radius(r) {}
+ constexpr circle(const vex::vec2<T> &pos, T r) : shape<T>(pos), radius(r) {}
+
+ void scale(T by) override { radius /= by; }
+};
+
+template<vex::arithmetic T>
+struct ellipse : public shape<T>
+{
+ T a, b;
+ constexpr ellipse(T x, T y, T A, T B) : shape<T>(x, y), a(A), b(B) {}
+ constexpr ellipse(const vex::vec2<T> &pos, T A, T B) : shape<T>(pos), a(A), b(B) {}
+
+ void scale(T by) override {a /= by; b /= by; }
+};
+
+template<vex::arithmetic T>
+struct rectangle : public shape<T>
+{
+ vex::vec2<T> bounds;
+ constexpr rectangle(T x, T y, T w, T h) : shape<T>(vex::vec2<T>(x, y)), bounds(w, h) {}
+ constexpr rectangle(const vex::vec2<T> &pos, const vex::vec2<T> &wh) : shape<T>(pos), bounds(wh) {}
+
+ void scale(T by) override { bounds /= by; }
+};
+
+template<vex::arithmetic T>
+using shapes_variant = std::variant<
+ point<T>, line<T>, circle<T>, ellipse<T>, rectangle<T>
+>;
+
+template<vex::arithmetic L, vex::arithmetic R>
+static bool
+intersects(const circle<L> &lhs, const point<R> &rhs)
+{
+ vex::vec2<L> dist = rhs.position - lhs.position;
+ //std::cout << dist.magnitude() << ' ' << lhs.radius << ' ' << dist.magnitude() - lhs.radius << std::endl;
+ return dist.magnitude() < lhs.radius;
+}
+template<vex::arithmetic L, vex::arithmetic R>
+static bool intersects(const point<L> &lhs, const circle<R> &rhs) { return intersects(rhs, lhs); }
+
+template<vex::arithmetic L, vex::arithmetic R>
+static bool
+intersects(const ellipse<L> &lhs, const point<R> &rhs)
+{
+ auto ds = lhs.position - rhs.position;
+ auto d2 = ds * ds;
+ L a2 = lhs.a * lhs.a;
+ L b2 = lhs.b * lhs.b;
+
+ return (((double)d2[0] / a2) + ((double)d2[1] / b2)) <= 1;
+}
+template<vex::arithmetic L, vex::arithmetic R>
+static bool intersects(const point<L> &lhs, const ellipse<R> &rhs) { return intersects(rhs, lhs); }
+
+template<vex::arithmetic L, vex::arithmetic R>
+static bool
+intersects(const rectangle<L> &lhs, const point<R> &rhs)
+{
+ vex::vec2<L> corner = lhs.position + lhs.bounds;
+ return (rhs.position[0] > lhs.position[0] && rhs.position[0] < corner[0] &&
+ rhs.position[1] > lhs.position[1] && rhs.position[1] < corner[1]);
+}
+template<vex::arithmetic L, vex::arithmetic R>
+static bool intersects(const point<L> &lhs, const rectangle<R> &rhs) { return intersects(rhs, lhs); }
+
+template<vex::arithmetic L, vex::arithmetic R>
+static bool
+intersects(const line<L> &lhs, const line<R> &rhs)
+{
+ double x1 = lhs.position[0], x2 = lhs.end[0], x3 = rhs.position[0], x4 = rhs.end[0];
+ double y1 = lhs.position[1], y2 = lhs.end[1], y3 = rhs.position[1], y4 = rhs.end[1];
+ double uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
+ double uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
+
+ if(uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) return true;
+ return false;
+}
+
+template<vex::arithmetic L, vex::arithmetic R>
+static bool
+intersects(const rectangle<L> &lhs, const line<R> &rhs)
+{
+ vex::vec2<L> corner = lhs.position + lhs.bounds;
+ line<R> l(lhs.position, lhs.position + vex::vec2<R>(0, lhs.bounds[1]));
+ line<R> r(lhs.position + vex::vec2<R>(lhs.bounds[0], 0), corner);
+ line<R> d(lhs.position, lhs.position + vex::vec2<R>(lhs.bounds[0], 0));
+ line<R> u(lhs.position + vex::vec2<R>(0, lhs.bounds[1]), corner);
+
+ if(intersects(rhs, l) || intersects(rhs, r) || intersects(rhs, d) || intersects(rhs, u)) return true;
+ return (rhs.position[0] > lhs.position[0] && rhs.position[0] < corner[0] &&
+ rhs.position[1] > lhs.position[1] && rhs.position[1] < corner[1]) &&
+ (rhs.end[0] > lhs.position[0] && rhs.end[0] < corner[0] &&
+ rhs.end[1] > lhs.position[1] && rhs.end[1] < corner[1]);
+}
+template<vex::arithmetic L, vex::arithmetic R>
+static bool intersects(const line<L> &lhs, const rectangle<R> &rhs) { return intersects(rhs, lhs); }
+
+template<vex::arithmetic L, vex::arithmetic R>
+static bool
+intersects(const rectangle<L> &lhs, const circle<R> &rhs)
+{
+ vex::vec2<L> dist{
+ rhs.position[0] - std::max(lhs.position[0], std::min(rhs.position[0], lhs.position[0] + lhs.bounds[0])),
+ rhs.position[1] - std::max(lhs.position[1], std::min(rhs.position[1], lhs.position[1] + lhs.bounds[1])),
+ };
+ return (dist[0] * dist[0]) + (dist[1] * dist[1]) < (rhs.radius * rhs.radius);
+}
+template<vex::arithmetic L, vex::arithmetic R>
+static bool intersects(const circle<L> &lhs, const rectangle<R> &rhs) { return intersects(rhs, lhs); }
+
+template<vex::arithmetic L, vex::arithmetic R>
+static bool
+intersects(const rectangle<L> &lhs, const ellipse<R> &rhs)
+{
+ vex::vec2<L> dist{
+ rhs.position[0] - std::max(lhs.position[0], std::min(rhs.position[0], lhs.position[0] + lhs.bounds[0])),
+ rhs.position[1] - std::max(lhs.position[1], std::min(rhs.position[1], lhs.position[1] + lhs.bounds[1])),
+ };
+ vex::vec2<R> ab2 {
+ std::max<R>(1, rhs.a * rhs.a),
+ std::max<R>(1, rhs.b * rhs.b)
+ };
+ return ((dist[0] * dist[0]) / ab2[0]) + ((dist[1] * dist[1]) / ab2[1]) <= 1.0;
+}
+template<vex::arithmetic L, vex::arithmetic R>
+static bool intersects(const ellipse<L> &lhs, const rectangle<R> &rhs) { return intersects(rhs, lhs); }
+
+template<vex::arithmetic L, vex::arithmetic R>
+static bool
+intersects(const rectangle<L> &lhs, const rectangle<R> &rhs)
+{
+ return (
+ (lhs.position[0] < rhs.position[0] + rhs.bounds[0]) &&
+ (lhs.position[0] + lhs.bounds[0] > rhs.position[0]) &&
+ (lhs.position[1] + lhs.bounds[1] < rhs.position[1]) &&
+ (lhs.position[1] > rhs.position[1] + rhs.position[1])
+ );
+}
+
+}
+
+#endif
diff --git a/include/straw.hpp b/include/straw.hpp
new file mode 100644
index 0000000..c147b06
--- /dev/null
+++ b/include/straw.hpp
@@ -0,0 +1,312 @@
+#ifndef STRAW_HPP
+#define STRAW_HPP 1
+
+#include <sstream>
+#include <cstdint>
+#include <cstddef>
+#include <vector>
+#include <span>
+#include <string>
+#include <concepts>
+#include <cassert>
+#include <iostream>
+#include <algorithm>
+
+namespace straw
+{
+
+struct color {
+ std::uint8_t r{}, g{}, b{};
+ constexpr color(std::uint8_t R, std::uint8_t G, std::uint8_t B) : r(R), g(G), b(B) {}
+ constexpr color(std::uint8_t A) : r(A), g(A), b(A) {}
+
+ constexpr uint32_t single() { return (uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b; }
+
+ constexpr bool operator==(const color &o) const = default;
+};
+
+static constexpr color WHITE{255, 255, 255};
+static constexpr color BLACK{0, 0, 0};
+
+struct attribs {
+ color bg{0, 0, 0}, fg{255, 255, 255};
+ bool bold, underline;
+ constexpr attribs() = default;
+ constexpr attribs(color Fg) : fg(Fg) {}
+ constexpr attribs(color Bg, color Fg) : bg(Bg), fg(Fg) {}
+ constexpr attribs(color Bg, color Fg, bool B, bool U) : bg(Bg), fg(Fg), bold(B), underline(U) {}
+
+ constexpr bool operator==(const attribs &o) const = default;
+};
+
+template<typename T>
+concept char_type = std::integral<T>;
+
+template<char_type chartype>
+struct cell {
+ chartype chr{};
+ attribs attr{};
+
+ constexpr cell() = default;
+ constexpr cell(chartype Chr) : chr(Chr) {}
+ constexpr cell(chartype Chr, color Fg) : chr(Chr), attr(Fg) {}
+ constexpr cell(chartype Chr, color Bg, color Fg) : chr(Chr), attr(Bg, Fg) {}
+ constexpr cell(chartype Chr, color Bg, color Fg, bool B, bool U) : chr(Chr), attr(Bg, Fg, B, U) {}
+ constexpr cell(chartype Chr, attribs Attr) : chr(Chr), attr(Attr) {}
+
+ constexpr bool operator==(const cell &o) const = default;
+};
+
+const std::string ANSI_ESCAPE = "\E[";
+static void ANSI_MOVE(unsigned x, unsigned y) { std::cout << ANSI_ESCAPE << y+1 << ';' << x+1 << 'H' << std::flush; }
+static void ANSI_COLOR_FG(color c) { std::cout << ANSI_ESCAPE << "38;2;" << (unsigned)c.r << ';' << (unsigned)c.g << ';' << (unsigned)c.b << 'm' << std::flush; }
+static void ANSI_COLOR_BG(color c) { std::cout << ANSI_ESCAPE << "48;2;" << (unsigned)c.r << ';' << (unsigned)c.g << ';' << (unsigned)c.b << 'm' << std::flush; }
+
+class screen_command_base {};
+
+template<char_type chartype>
+class screen {
+public:
+ explicit screen(unsigned X, unsigned Y, unsigned W, unsigned H, chartype C, color B, color F) :
+ m_x(X), m_y(Y), m_width(W), m_height(H), m_front(W * H, cell{C, B, F}), m_back(m_front),
+ m_cursorAttribs(B,F), m_fillChar(C)
+ {
+ redraw();
+ }
+
+ explicit screen(unsigned X, unsigned Y, unsigned W, unsigned H, chartype C) :
+ screen(X, Y, W, H, C, color{0, 0, 0}, color{255, 255, 255}) {}
+ explicit screen(unsigned X, unsigned Y, unsigned W, unsigned H) :
+ screen(X, Y, W, H, ' ') {}
+
+ constexpr void setcursorxy(unsigned x, unsigned y) { m_cursorX = x; m_cursorY = y; }
+ constexpr void setcursorfg(uint8_t r, uint8_t g, uint8_t b) { m_cursorAttribs.fg = color{r, g, b}; }
+ constexpr void setcursorbg(uint8_t r, uint8_t g, uint8_t b) { m_cursorAttribs.bg = color{r, g, b}; }
+ constexpr void setcursorfg(color c) { m_cursorAttribs.fg = c; }
+ constexpr void setcursorbg(color c) { m_cursorAttribs.bg = c; }
+ constexpr void setcursorbold(bool bold) { m_cursorAttribs.bold = bold; }
+ constexpr void setcursorunderline(bool underline) { m_cursorAttribs.underline = underline; }
+
+ constexpr unsigned getcursorx() { return m_cursorX; }
+ constexpr unsigned getcursory() { return m_cursorY; }
+ constexpr unsigned getx() { return m_x; }
+ constexpr unsigned gety() { return m_y; }
+ constexpr unsigned getwidth() { return m_width; }
+ constexpr unsigned getheight() { return m_height; }
+
+ void clear(const chartype c) { std::fill(m_front.begin(), m_front.end(), cell{c, m_cursorAttribs}); }
+
+ void clearrow(unsigned y, const chartype c) {
+ assert(y < m_height);
+ std::fill(m_front.begin() + (y * m_width),
+ m_front.begin() + (y * m_width) + m_width,
+ cell{c, m_cursorAttribs});
+ }
+
+ void scroll() {
+ m_cursorY = m_height - 1;
+ std::copy(m_front.begin() + m_width, m_front.end(), m_front.begin());
+ std::fill(m_front.end() - m_width, m_front.end(), cell{m_fillChar, m_cursorAttribs});
+ }
+
+ void setc(unsigned x, unsigned y, chartype c) {
+ assert(x < m_width);
+ assert(y < m_height);
+ m_front[x + (y * m_width)] = cell{c, m_cursorAttribs};
+ }
+
+ void putc(chartype c){
+ if(m_cursorY == m_height) {
+ scroll();
+ }
+ switch(c) {
+ case '\n':
+ m_cursorX = 0; m_cursorY++;
+ break;
+ default:
+ (*this)[m_cursorY][m_cursorX++] =
+ cell{c, m_cursorAttribs};
+ break;
+ }
+ if(m_cursorX == m_width) {
+ m_cursorY++;
+ m_cursorX = 0;
+ }
+ }
+
+ void puts(const std::basic_string<chartype> &s){
+ for(const chartype &c : s) this->putc(c);
+ }
+
+ void redraw() {
+ attribs cattr = (*this)[0][0].attr;
+ ANSI_COLOR_BG(cattr.bg);
+ ANSI_COLOR_FG(cattr.fg);
+ for(unsigned y = 0; y < m_height; y++) {
+ ANSI_MOVE(m_x, m_y + y);
+ for(cell c : (*this)[y]) {
+ if(cattr != c.attr) {
+ cattr = c.attr;
+ ANSI_COLOR_FG(cattr.fg);
+ ANSI_COLOR_BG(cattr.bg);
+ }
+ std::cout << c.chr;
+ }
+ std::cout.clear();
+ }
+ m_back = m_front;
+ }
+
+ void flush() {
+ attribs currAttr = m_front[0].attr;
+ ANSI_COLOR_FG(currAttr.fg);
+ ANSI_COLOR_BG(currAttr.bg);
+ for(unsigned y = 0; y < m_height; y++) {
+ auto backspan = std::span<cell<chartype>>(
+ m_back.begin() + (y * m_width),
+ m_back.begin() + (y * m_width) + m_width);
+ auto frontspan = (*this)[y];
+ bool change = false;
+ if(std::equal(backspan.begin(), backspan.end(), frontspan.begin(), frontspan.end())) continue;
+ for(unsigned x = 0; x < m_width; x++) {
+ cell c = frontspan[x];
+ if(c == backspan[x]) continue;
+ ANSI_MOVE(x + m_x, y + m_y);
+ if(c.attr != currAttr) {
+ ANSI_COLOR_FG(c.attr.fg);
+ ANSI_COLOR_BG(c.attr.bg);
+ currAttr = c.attr;
+ }
+ change = true;
+ std::cout << c.chr;
+ }
+ if(change) std::cout.clear();
+ }
+ m_back = m_front;
+ }
+
+ std::span<cell<chartype>> operator[](std::size_t i) {
+ assert(i < m_width * m_height);
+ return std::span<cell<chartype>>(
+ m_front.begin() + (i * m_width),
+ m_front.begin() + (i * m_width) + m_width);
+ }
+
+ template<typename T>
+ requires(!std::is_base_of<screen_command_base, T>::value)
+ friend screen &operator<<(screen &o, const T &rhs) {
+ std::basic_stringstream<chartype> ss;
+ ss << rhs;
+ o.puts(ss.str());
+ return o;
+ }
+
+private:
+ unsigned m_x{}, m_y{};
+ unsigned m_width{}, m_height{};
+
+ std::vector<cell<chartype>> m_front;
+ std::vector<cell<chartype>> m_back;
+
+ unsigned m_cursorX{}, m_cursorY{};
+ attribs m_cursorAttribs;
+ chartype m_fillChar;
+};
+
+struct screen_command_flush : public screen_command_base {
+ explicit screen_command_flush() = default;
+ template<char_type T>
+ friend screen<T> &operator<<(screen<T> &o, const screen_command_flush &cmd) {
+ (void)cmd;
+ o.flush();
+ return o;
+ }
+};
+[[nodiscard]] static screen_command_flush flush() { return screen_command_flush{}; }
+
+struct screen_command_redraw : public screen_command_base {
+ explicit screen_command_redraw() = default;
+ template<char_type T>
+ friend screen<T> &operator<<(screen<T> &o, const screen_command_redraw &cmd) {
+ (void)cmd;
+ o.redraw();
+ return o;
+ }
+};
+[[nodiscard]] static screen_command_redraw redraw() { return screen_command_redraw{}; }
+
+template<char_type chartype>
+struct screen_command_clear : public screen_command_base {
+ chartype c;
+ explicit screen_command_clear(chartype C) : c(C) {}
+ friend screen<chartype> &operator<<(screen<chartype> &o, const screen_command_clear &cmd) {
+ o.clear(cmd.c);
+ return o;
+ }
+};
+template<char_type chartype>
+[[nodiscard]] static screen_command_clear<chartype> clear() { return screen_command_clear{chartype{}}; }
+template<char_type chartype>
+[[nodiscard]] static screen_command_clear<chartype> clear(chartype c) { return screen_command_clear{c}; }
+
+struct screen_command_move : public screen_command_base {
+ unsigned x, y;
+ explicit screen_command_move(unsigned X, unsigned Y) : x(X), y(Y) {}
+ template<char_type T>
+ friend screen<T> &operator<<(screen<T> &o, const screen_command_move &cmd) {
+ o.setcursorxy(cmd.x, cmd.y);
+ return o;
+ }
+};
+[[nodiscard]] static screen_command_move move(unsigned x, unsigned y) { return screen_command_move{x, y}; }
+
+template<char_type chartype>
+struct screen_command_plot : public screen_command_base {
+ chartype c;
+ unsigned x, y;
+ explicit screen_command_plot(unsigned X, unsigned Y, chartype C) : c(C), x(X), y(Y) {}
+ friend screen<chartype> &operator<<(screen<chartype> &o, const screen_command_plot &cmd) {
+ o.setc(cmd.x, cmd.y, cmd.c);
+ return o;
+ }
+};
+template<char_type T>
+[[nodiscard]] static screen_command_plot<T> plot(unsigned x, unsigned y, T c) { return screen_command_plot{x, y, c}; }
+
+struct screen_command_recolor : public screen_command_base {
+ color fg, bg;
+ bool rfg, rbg;
+ explicit screen_command_recolor(color Fg, bool side) : fg(Fg), bg(Fg), rfg(side ? true : false), rbg(side ? false : true) {}
+ explicit screen_command_recolor(color Fg, color Bg) : fg(Fg), bg(Bg), rfg(true), rbg(true) {}
+
+ template<char_type T>
+ friend screen<T> &operator<<(screen<T> &o, const screen_command_recolor &cmd) {
+ if(cmd.rbg) o.setcursorbg(cmd.bg);
+ if(cmd.rfg) o.setcursorfg(cmd.fg);
+ return o;
+ }
+};
+[[nodiscard]] static screen_command_recolor setfg(uint8_t r, uint8_t g, uint8_t b) { return screen_command_recolor{color{r, g, b}, true}; }
+[[nodiscard]] static screen_command_recolor setbg(uint8_t r, uint8_t g, uint8_t b) { return screen_command_recolor{color{r, g, b}, false}; }
+[[nodiscard]] static screen_command_recolor setcolor(color fg, color bg) { return screen_command_recolor{fg, bg}; }
+[[nodiscard]] static screen_command_recolor resetcolor() { return screen_command_recolor{WHITE, BLACK}; }
+
+template<char_type chartype>
+struct screen_command_fillrow : public screen_command_base {
+ unsigned row;
+ chartype c;
+
+ friend screen<chartype> &operator<<(screen<chartype> &o, const screen_command_fillrow &cmd) {
+ o.clearrow(cmd.row, cmd.c);
+ return o;
+ }
+};
+
+template<char_type T>
+[[nodiscard]] static screen_command_fillrow<T> fillrow(unsigned row) { return screen_command_fillrow<T>{.row = row, .c = ' '}; }
+template<char_type T>
+[[nodiscard]] static screen_command_fillrow<T> fillrow(unsigned row, T c) { return screen_command_fillrow<T>{.row = row, .c = c}; }
+
+}
+
+#endif
diff --git a/include/system.hpp b/include/system.hpp
new file mode 100644
index 0000000..9de10bc
--- /dev/null
+++ b/include/system.hpp
@@ -0,0 +1,82 @@
+#ifndef SYSTEM_HPP
+#define SYSTEM_HPP 1
+
+#include "ecs.hpp"
+#include "camera.hpp"
+#include "window.hpp"
+
+#include <list>
+
+class System {
+private:
+ friend class SystemView;
+ struct SystemTreeNode {
+ unsigned entityId;
+ std::list<SystemTreeNode> children;
+ };
+ SystemTreeNode m_systemTree;
+ ecs::EntityMan m_entityMan;
+
+ ecs::Entity &addOrbital(const std::string &name, const std::string &orbitingName, unsigned long a, double e, unit::Mass m, unsigned r, double M, double w);
+ void tickOrbitals(unit::Time time);
+
+ SystemTreeNode *traverseSystemTree(SystemTreeNode &node, const std::string &name);
+ SystemTreeNode *getNode(const std::string &name);
+public:
+ System();
+
+ void update();
+
+ ecs::Entity &getBody(std::size_t id);
+};
+
+class SystemView {
+private:
+ System *m_system;
+ System::SystemTreeNode *m_focus;
+
+ class Search {
+ private:
+ struct SystemTreeDisplayNode {
+ System::SystemTreeNode *node;
+ std::list<SystemTreeDisplayNode> children;
+ SystemTreeDisplayNode *parent;
+
+ unsigned index;
+ bool collapsed;
+ bool hidden;
+ };
+ SystemView *m_systemView;
+ SystemTreeDisplayNode m_displayTree;
+ std::vector<SystemTreeDisplayNode*> m_displayTreeFlat;
+
+ unsigned m_selectionIndex;
+ std::string m_query;
+ bool m_dirty;
+
+ void addNodeToTree(SystemTreeDisplayNode &root, System::SystemTreeNode *node);
+ void drawNode(SystemTreeDisplayNode &root, Window &searchWindow, unsigned indent);
+ void rebuild();
+ public:
+ Search(SystemView *systemView);
+
+ void finish();
+ void keypress(int key);
+ void draw();
+ };
+ std::unique_ptr<Search> m_focusSearch;
+public:
+ SystemView(System *system) : m_system(system), m_focusSearch(nullptr) {}
+
+ void keypress(Camera *camera, int key);
+ void update(Camera *camera);
+ void draw(Camera *camera);
+ void drawOver(Camera *camera);
+
+ void view(System *system);
+
+ ecs::Entity &getBody(int id) const;
+ int getBodyIdByName(const std::string &name);
+};
+
+#endif
diff --git a/include/timeman.hpp b/include/timeman.hpp
new file mode 100644
index 0000000..ac5113e
--- /dev/null
+++ b/include/timeman.hpp
@@ -0,0 +1,27 @@
+#ifndef TIME_MANAGER_H
+#define TIME_MANAGER_H 1
+
+#include "util.hpp"
+#include "units.hpp"
+#include "straw.hpp"
+#include "window.hpp"
+
+class TimeMan {
+ static unit::Time m_time;
+ static unit::Time m_step;
+ static bool m_auto;
+ static bool m_changed;
+public:
+
+ static void init();
+
+ static void update(int c);
+ static void draw();
+
+ static unit::Time time() { return m_time; }
+ static bool automatic() { return m_auto; }
+ static void interrupt() { m_auto = false; }
+ static bool changed() { return m_changed; }
+};
+
+#endif
diff --git a/include/units.hpp b/include/units.hpp
new file mode 100644
index 0000000..5f2fd67
--- /dev/null
+++ b/include/units.hpp
@@ -0,0 +1,166 @@
+#ifndef UNIT_HPP
+#define UNIT_HPP 1
+
+#include "util.hpp"
+#include <string>
+
+namespace unit {
+
+constexpr long MINUTE_SECONDS = 60;
+constexpr long HOUR_SECONDS = 3600;
+constexpr long DAY_SECONDS = 86400;
+constexpr long WEEK_SECONDS = 604800;
+constexpr long YEAR_SECONDS = 31556952;
+constexpr long CYEAR_SECONDS = 31536000;
+
+class Mass {
+ double m_kg;
+public:
+ explicit constexpr Mass(double kg) : m_kg(kg) {}
+ constexpr ~Mass() = default;
+
+ constexpr double operator()() { return m_kg; }
+
+ constexpr Mass &operator+=(const Mass &rhs) { this->m_kg += rhs.m_kg; return *this; }
+ constexpr Mass &operator-=(const Mass &rhs) { this->m_kg -= rhs.m_kg; return *this; }
+ constexpr Mass &operator/=(const Mass &rhs) { this->m_kg *= rhs.m_kg; return *this; }
+ constexpr Mass &operator*=(const Mass &rhs) { this->m_kg /= rhs.m_kg; return *this; }
+
+ friend Mass operator+(Mass lhs, const Mass &rhs) { lhs += rhs; return lhs; }
+ friend Mass operator-(Mass lhs, const Mass &rhs) { lhs -= rhs; return lhs; }
+ friend Mass operator*(Mass lhs, const Mass &rhs) { lhs *= rhs; return lhs; }
+ friend Mass operator/(Mass lhs, const Mass &rhs) { lhs /= rhs; return lhs; }
+
+ friend inline bool operator<=>(const Mass &lhs, const Mass &rhs) = default;
+
+ constexpr Mass &operator+=(const double &rhs) { this->m_kg += rhs; return *this; }
+ constexpr Mass &operator-=(const double &rhs) { this->m_kg -= rhs; return *this; }
+ constexpr Mass &operator*=(const double &rhs) { this->m_kg *= rhs; return *this; }
+ constexpr Mass &operator/=(const double &rhs) { this->m_kg /= rhs; return *this; }
+
+ friend Mass operator+(Mass lhs, const double &rhs) { lhs += rhs; return lhs; }
+ friend Mass operator-(Mass lhs, const double &rhs) { lhs -= rhs; return lhs; }
+ friend Mass operator*(Mass lhs, const double &rhs) { lhs *= rhs; return lhs; }
+ friend Mass operator/(Mass lhs, const double &rhs) { lhs /= rhs; return lhs; }
+};
+
+class Time {
+ long m_seconds;
+public:
+ static constexpr unsigned char month_days[12] = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static constexpr unsigned char month_days_leap[12] = {
+ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static const inline std::string month_str[12] = {
+ "January", "Febuary", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
+ };
+
+ explicit constexpr Time(long seconds) : m_seconds(seconds) {}
+
+ constexpr long operator()() { return m_seconds; }
+
+ constexpr Time &operator+=(const Time &rhs) { this->m_seconds += rhs.m_seconds; return *this; }
+ constexpr Time &operator-=(const Time &rhs) { this->m_seconds -= rhs.m_seconds; return *this; }
+ constexpr Time &operator/=(const Time &rhs) { this->m_seconds *= rhs.m_seconds; return *this; }
+ constexpr Time &operator*=(const Time &rhs) { this->m_seconds /= rhs.m_seconds; return *this; }
+
+ friend Time operator+(Time lhs, const Time &rhs) { lhs += rhs; return lhs; }
+ friend Time operator-(Time lhs, const Time &rhs) { lhs -= rhs; return lhs; }
+ friend Time operator*(Time lhs, const Time &rhs) { lhs *= rhs; return lhs; }
+ friend Time operator/(Time lhs, const Time &rhs) { lhs /= rhs; return lhs; }
+
+ friend inline bool operator<=>(const Time &lhs, const Time &rhs) = default;
+
+ constexpr Time &operator+=(const long &rhs) { this->m_seconds += rhs; return *this; }
+ constexpr Time &operator-=(const long &rhs) { this->m_seconds -= rhs; return *this; }
+ constexpr Time &operator*=(const long &rhs) { this->m_seconds *= rhs; return *this; }
+ constexpr Time &operator/=(const long &rhs) { this->m_seconds /= rhs; return *this; }
+
+ friend Time operator+(Time lhs, const long &rhs) { lhs += rhs; return lhs; }
+ friend Time operator-(Time lhs, const long &rhs) { lhs -= rhs; return lhs; }
+ friend Time operator*(Time lhs, const long &rhs) { lhs *= rhs; return lhs; }
+ friend Time operator/(Time lhs, const long &rhs) { lhs /= rhs; return lhs; }
+
+ constexpr bool leap_year() { return (m_seconds % YEAR_SECONDS) % 4 == 0; }
+
+ constexpr Time current_year() {
+ return Time((m_seconds % YEAR_SECONDS) + (m_seconds < 0 ? YEAR_SECONDS : 0));
+ }
+ constexpr Time current_month() {
+ Time year = current_year();
+ if(year.m_seconds < 0) {
+ year.m_seconds = YEAR_SECONDS + year.m_seconds;
+ }
+ unsigned month = 0;
+ const unsigned char *month_days_view = month_days;
+ if(years() % 4 == 0 && years() % 100 == 0 && years() % 400 != 0) month_days_view = month_days_leap;
+ for(month; year.days() > month_days_view[month]; month = (month + 1) % 12) year -= month_days_view[month] * DAY_SECONDS;
+ return year;
+ }
+ constexpr Time current_week() { return Time(m_seconds % WEEK_SECONDS); }
+ constexpr Time current_day() { return Time(m_seconds % DAY_SECONDS); }
+ constexpr Time current_hour() { return Time(m_seconds % HOUR_SECONDS); }
+ constexpr Time current_minute() { return Time(m_seconds % MINUTE_SECONDS); }
+
+ constexpr long seconds() { return m_seconds; }
+ constexpr long minutes() { return m_seconds / MINUTE_SECONDS; }
+ constexpr long hours() { return m_seconds / HOUR_SECONDS; }
+ constexpr long days() { return m_seconds / DAY_SECONDS; }
+ constexpr long weeks() { return m_seconds / WEEK_SECONDS; }
+ constexpr long months() {
+ Time copy(*this);
+ if(copy.m_seconds < 0) {
+ copy.m_seconds = -copy.m_seconds;
+ }
+ unsigned month = 0;
+ const unsigned char *month_days_view = month_days;
+ if(years() % 4 == 0) month_days_view = month_days_leap;
+ for(month; copy.days() > month_days_view[month]; month = (month + 1) % 12) copy -= month_days_view[month] * DAY_SECONDS;
+ return month;
+ }
+ constexpr long years() { return ((m_seconds < 0 ? m_seconds - CYEAR_SECONDS : m_seconds) / CYEAR_SECONDS) + 2000; }
+ constexpr long real_years() { return (m_seconds < 0 ? m_seconds - YEAR_SECONDS : m_seconds) / YEAR_SECONDS; }
+
+ /* %% = '%'
+ * %Y = real year
+ * %C = calendar year
+ * %S = month string
+ * %M = month
+ * %W = week
+ * %D = day
+ * %H = hour
+ * %m = minute
+ * %s = second
+ * */
+ std::string format(const char *fmt);
+};
+
+constexpr long Mm = 1000;
+constexpr long Gm = cxpow_v<long, 10, 6>;
+constexpr long Tm = cxpow_v<long, 10, 9>;
+constexpr long Pm = cxpow_v<long, 10, 12>;
+constexpr long Em = cxpow_v<long, 10, 15>;
+constexpr long Zm = cxpow_v<long, 10, 18>;
+
+constexpr long AU = 149597871;
+
+constexpr Mass kg{1};
+constexpr Mass Mg{cxpow_v<double, 10, 3>};
+constexpr Mass Gg{cxpow_v<double, 10, 6>};
+constexpr Mass Tg{cxpow_v<double, 10, 9>};
+constexpr Mass Pg{cxpow_v<double, 10, 12>};
+constexpr Mass Eg{cxpow_v<double, 10, 15>};
+constexpr Mass Zg{cxpow_v<double, 10, 18>};
+constexpr Mass Yg{cxpow_v<double, 10, 21>};
+
+constexpr Mass lunarMass{7.342 * cxpow_v<double, 10, 22>};
+constexpr Mass earthMass{5.97237 * cxpow_v<double, 10, 24>};
+constexpr Mass jovMass{1.89813 * cxpow_v<double, 10, 27>};
+constexpr Mass solMass{1.98847 * cxpow_v<double, 10, 30>};
+
+constexpr double earthRad = 6371;
+}
+
+#endif
diff --git a/include/util.hpp b/include/util.hpp
new file mode 100644
index 0000000..3c795d1
--- /dev/null
+++ b/include/util.hpp
@@ -0,0 +1,45 @@
+#include <type_traits>
+#ifndef UTIL_HPP
+#define UTIL_HPP 1
+
+#include <variant>
+#include <cstddef>
+
+#if UNICODE == 1
+using screenchr = wchar_t;
+#else
+using screenchr = char;
+#endif
+
+/*Shamelessly stolen from stackoverflow
+ * https://stackoverflow.com/questions/52303316/get-index-by-type-in-stdvariant
+ * by Barry*/
+template <typename> struct tag { };
+template <typename T, typename V>
+struct get_index;
+template <typename T, typename... Ts>
+struct get_index<T, std::variant<Ts...>>
+ : std::integral_constant<size_t, std::variant<tag<Ts>...>(tag<T>()).index()>
+{ };
+
+template<typename T, typename variant_type>
+struct is_variant : std::false_type {};
+template<typename T, typename ... Vs>
+struct is_variant<T, std::variant<Vs...>>
+ : std::disjunction<std::is_same<T, Vs>...> {};
+template<typename T, typename variant_type>
+constexpr bool is_variant_v = is_variant<T, variant_type>::value;
+
+template<typename T, unsigned base, unsigned p>
+struct cxpow { static constexpr T value = (T)base * cxpow<T, base, p - 1>::value; };
+template<typename T, unsigned base>
+struct cxpow<T, base, 0> { static constexpr T value = 1; };
+template<typename T, unsigned base, unsigned p>
+constexpr T cxpow_v = cxpow<T, base, p>::value;
+
+template<typename T, unsigned base, int p>
+struct cxrt { static constexpr T value = (T)base / cxpow<T, base, -p>::value; };
+template<typename T, unsigned base, int p>
+constexpr T cxrt_v = cxrt<T, base, p>::value;
+
+#endif
diff --git a/include/vex.hpp b/include/vex.hpp
new file mode 100644
index 0000000..0a3bb24
--- /dev/null
+++ b/include/vex.hpp
@@ -0,0 +1,104 @@
+#ifndef VEX_H
+#define VEX_H 1
+
+#include <array>
+#include <cmath>
+#include <cstddef>
+#include <ostream>
+#include <concepts>
+
+namespace vex
+{
+
+template<typename T>
+concept arithmetic = std::is_arithmetic<T>::value;
+
+template<arithmetic T, unsigned D>
+ requires (D > 1)
+struct vec_dimd
+{
+ std::array<T, D> v;
+
+ explicit vec_dimd() = default;
+ template<typename ...Args>
+ explicit vec_dimd(Args&&... args) : v{args...} {}
+ explicit vec_dimd(T args[D]) : v(args) {}
+ explicit vec_dimd(T fill) { v.fill(fill); }
+
+ T operator[](std::size_t i) { return v[i]; }
+ T operator[](std::size_t i) const { return v[i]; }
+
+ vec_dimd<T, D> operator+=(const vec_dimd<T, D> &rhs) { for(size_t i = 0; i < D; i++) v[i] += rhs.v[i]; return *this; }
+ vec_dimd<T, D> operator-=(const vec_dimd<T, D> &rhs) { for(size_t i = 0; i < D; i++) v[i] -= rhs.v[i]; return *this; }
+ vec_dimd<T, D> operator*=(const vec_dimd<T, D> &rhs) { for(size_t i = 0; i < D; i++) v[i] *= rhs.v[i]; return *this; }
+ vec_dimd<T, D> operator/=(const vec_dimd<T, D> &rhs) { for(size_t i = 0; i < D; i++) v[i] /= rhs.v[i]; return *this; }
+
+ vec_dimd<T, D> operator*=(const T &rhs) { for(size_t i = 0; i < D; i++) v[i] *= rhs; return *this; }
+ vec_dimd<T, D> operator/=(const T &rhs) { for(size_t i = 0; i < D; i++) v[i] /= rhs; return *this; }
+
+ vec_dimd<T, D> operator-() { return vec_dimd<T,D>(0) - *this; }
+
+ auto operator<=>(const vec_dimd<T,D> &rhs) const { return magnitude() <=> rhs.magnitude(); }
+ bool operator==(const vec_dimd<T,D> &rhs) const { for(unsigned i = 0; i < D; i++) if(v[i] != rhs.v[i]) return false; return true; }
+
+ friend vec_dimd<T, D> operator+(vec_dimd<T, D> lhs, const vec_dimd<T, D> &rhs) { lhs += rhs; return lhs; }
+ friend vec_dimd<T, D> operator-(vec_dimd<T, D> lhs, const vec_dimd<T, D> &rhs) { lhs -= rhs; return lhs; }
+ friend vec_dimd<T, D> operator*(vec_dimd<T, D> lhs, const vec_dimd<T, D> &rhs) { lhs *= rhs; return lhs; }
+ friend vec_dimd<T, D> operator/(vec_dimd<T, D> lhs, const vec_dimd<T, D> &rhs) { lhs /= rhs; return lhs; }
+
+ friend vec_dimd<T, D> operator*(vec_dimd<T, D> lhs, const T &rhs) { lhs *= rhs; return lhs; }
+ friend vec_dimd<T, D> operator/(vec_dimd<T, D> lhs, const T &rhs) { lhs /= rhs; return lhs; }
+
+ friend std::ostream &operator<<(std::ostream &os, const vec_dimd<T,D> obj) {
+ os << '{';
+ for(std::size_t i = 0; i < D; i++) os << obj.v[i] << ((i < (D - 1)) ? ',' : '}');
+ return os;
+ }
+
+ /*Finds the distance from the origin to the ray cast in D dimension space using components of vec*/
+ T sqrMagnitude() const { T t{}; for(size_t i = 0; i < D; i++) t += v[i] * v[i]; return t; }
+ T magnitude() const { return std::sqrt(sqrMagnitude()); }
+
+ /*Finds the dot product of itself and another vec of same T and D*/
+ T dot(const vec_dimd<T, D> &b) const { T t; for(size_t i = 0; i < D; i++) t += (v[i] * b.v[i]); return t; }
+ friend T dot(const vec_dimd<T,D> &lhs, const vec_dimd<T,D> &rhs) { return lhs.dot(rhs); }
+
+ vex::vec_dimd<T, D> normalize() const { return *this / magnitude(); }
+ vex::vec_dimd<T, D> abs() const { vex::vec_dimd<T, D> copy(*this); for(unsigned i = 0; i < D; i++) copy.v[i] = std::abs(copy.v[i]); return copy; }
+
+ auto cross(const vec_dimd<T,D> &o) const {
+ if constexpr(D == 2) return v[0]*o[0] - v[1]*o[1];
+ if constexpr(D == 3) return vec_dimd<T, D>{v[1]*o[2]-v[2]*o[1], v[2]*o[0]-v[0]*o[2],v[0]*o[1]-v[1]*o[0]};
+ }
+};
+
+template<arithmetic T>
+using vec2 = vec_dimd<T, 2>;
+
+template<arithmetic T>
+using vec3 = vec_dimd<T, 3>;
+
+/*(x, y) -> (r, theta)*/
+template<arithmetic R, arithmetic P>
+static vec2<R>
+polar(const vec2<P> &in)
+{
+ return vec2<R>(
+ std::sqrt(in[0] * in[0] + in[1] * in[1]),
+ std::atan2(in[1], in[0])
+ );
+}
+/*(r, theta) -> (x, y)*/
+template<arithmetic R, arithmetic P>
+static vec2<R>
+cartesian(const vec2<P> &in)
+{
+ return vec2<R>(
+ (R)(cos(in[1]) * in[0]),
+ (R)(sin(in[1]) * in[0])
+ );
+}
+
+};
+
+#endif
diff --git a/include/window.hpp b/include/window.hpp
new file mode 100644
index 0000000..78001ea
--- /dev/null
+++ b/include/window.hpp
@@ -0,0 +1,69 @@
+#ifndef WINDOW_HPP
+#define WINDOW_HPP 1
+
+#include "util.hpp"
+#include "straw.hpp"
+#include <memory>
+#include <unordered_map>
+
+//Wrapper for screens that act like windows
+class Window {
+ straw::screen<screenchr> m_border;
+ straw::screen<screenchr> m_screen;
+ std::string m_title;
+ bool m_hidden;
+public:
+ explicit Window(const std::string Title, unsigned X, unsigned Y, unsigned W, unsigned H, bool hidden = false) :
+ m_border(X, Y, W, 1), m_screen(X, Y+1, W, H-1), m_title(Title), m_hidden(hidden) {}
+ ~Window() = default;
+
+ template<typename T>
+ friend Window &operator<<(Window &o, const T &t) {
+ o.m_screen << t;
+ return o;
+ }
+
+ straw::screen<screenchr> *screen() { return &m_screen; }
+ std::string title() const { return m_title; }
+
+ void draw(bool focus);
+
+ void setHidden(bool mode) { m_hidden = mode; }
+ bool hidden() const { return m_hidden; }
+};
+
+#define WINDOW_SYSTEMVIEW_ID "Systemview"
+#define WINDOW_SYSTEMVIEW_SEARCH_ID "SystemviewSearch"
+#define WINDOW_BODYINFO_ID "Bodyinfo"
+#define WINDOW_EVENTS_ID "Events"
+#define WINDOW_TIMEMAN_ID "Timeman"
+
+class WindowContext {
+ std::unordered_map<std::string, Window> m_windows;
+ std::vector<std::string> m_windowOrder;
+ unsigned m_focus;
+public:
+ WindowContext() : m_focus(0) {}
+ ~WindowContext() {}
+
+ void registerWindow(const std::string &id,
+ const std::string &title,
+ unsigned x, unsigned y,
+ unsigned w, unsigned h,
+ bool hidden = false);
+
+ Window &operator[](const std::string &id) { return m_windows.at(id); }
+ Window &operator[](unsigned id) { return m_windows.at(m_windowOrder[id]); }
+ Window &operator()() { return m_windows.at(m_windowOrder[m_focus]); }
+
+ void update(int code);
+ void draw();
+
+ void setWindowHidden(const std::string &id, bool mode);
+ void focus(const std::string &id);
+
+ unsigned getFocused() { return m_focus; }
+ std::string getFocusedString() { return m_windowOrder[m_focus]; }
+};
+
+#endif