aboutsummaryrefslogtreecommitdiff
path: root/src/openvic/types
diff options
context:
space:
mode:
author Hop311 <hop3114@gmail.com>2023-08-24 01:32:23 +0200
committer Hop311 <hop3114@gmail.com>2023-08-24 01:32:23 +0200
commitefa88c722fcb6c8fea7a86e1b3b8a83f1f59eb31 (patch)
tree0d64199bf4f19fabaaff083b35aa8625823d6c1e /src/openvic/types
parent6f4a6c77c6f2613e65a403c3a2964d5041a538c7 (diff)
Big Dataloader Commit (openvic-simulation)
Diffstat (limited to 'src/openvic/types')
-rw-r--r--src/openvic/types/Colour.hpp27
-rw-r--r--src/openvic/types/Date.cpp240
-rw-r--r--src/openvic/types/Date.hpp90
-rw-r--r--src/openvic/types/IdentifierRegistry.cpp37
-rw-r--r--src/openvic/types/IdentifierRegistry.hpp142
-rw-r--r--src/openvic/types/Return.hpp8
-rw-r--r--src/openvic/types/fixed_point/FP.hpp563
-rw-r--r--src/openvic/types/fixed_point/FPLUT.hpp33
-rw-r--r--src/openvic/types/fixed_point/FPLUT_sin_512.hpp58
-rw-r--r--src/openvic/types/fixed_point/FPMath.hpp11
-rw-r--r--src/openvic/types/fixed_point/lut_generator/lut_generator.py39
11 files changed, 1248 insertions, 0 deletions
diff --git a/src/openvic/types/Colour.hpp b/src/openvic/types/Colour.hpp
new file mode 100644
index 0000000..fdac40e
--- /dev/null
+++ b/src/openvic/types/Colour.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <algorithm>
+#include <cstdint>
+
+namespace OpenVic {
+ // Represents a 24-bit RGB integer OR a 32-bit ARGB integer
+ using colour_t = uint32_t;
+ /* When colour_t is used as an identifier, NULL_COLOUR is disallowed
+ * and should be reserved as an error value.
+ * When colour_t is used in a purely graphical context, NULL_COLOUR
+ * should be allowed.
+ */
+ static constexpr colour_t NULL_COLOUR = 0, FULL_COLOUR = 0xFF, MAX_COLOUR_RGB = 0xFFFFFF;
+ constexpr colour_t float_to_colour_byte(float f, float min = 0.0f, float max = 1.0f) {
+ return static_cast<colour_t>(std::clamp(min + f * (max - min), min, max) * 255.0f);
+ }
+ constexpr colour_t fraction_to_colour_byte(int n, int d, float min = 0.0f, float max = 1.0f) {
+ return float_to_colour_byte(static_cast<float>(n) / static_cast<float>(d), min, max);
+ }
+ constexpr colour_t float_to_alpha_value(float a) {
+ return float_to_colour_byte(a) << 24;
+ }
+ constexpr float colour_byte_to_float(colour_t colour) {
+ return std::clamp(static_cast<float>(colour) / 255.0f, 0.0f, 1.0f);
+ }
+}
diff --git a/src/openvic/types/Date.cpp b/src/openvic/types/Date.cpp
new file mode 100644
index 0000000..b6e1367
--- /dev/null
+++ b/src/openvic/types/Date.cpp
@@ -0,0 +1,240 @@
+#include "Date.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <cctype>
+#include <charconv>
+
+#include "openvic/utility/Logger.hpp"
+
+using namespace OpenVic;
+
+Timespan::Timespan(day_t value) : days { value } {}
+
+bool Timespan::operator<(Timespan other) const { return days < other.days; };
+bool Timespan::operator>(Timespan other) const { return days > other.days; };
+bool Timespan::operator<=(Timespan other) const { return days <= other.days; };
+bool Timespan::operator>=(Timespan other) const { return days >= other.days; };
+bool Timespan::operator==(Timespan other) const { return days == other.days; };
+bool Timespan::operator!=(Timespan other) const { return days != other.days; };
+
+Timespan Timespan::operator+(Timespan other) const { return days + other.days; }
+
+Timespan Timespan::operator-(Timespan other) const { return days - other.days; }
+
+Timespan Timespan::operator*(day_t factor) const { return days * factor; }
+
+Timespan Timespan::operator/(day_t factor) const { return days / factor; }
+
+Timespan& Timespan::operator+=(Timespan other) {
+ days += other.days;
+ return *this;
+}
+
+Timespan& Timespan::operator-=(Timespan other) {
+ days -= other.days;
+ return *this;
+}
+
+Timespan& Timespan::operator++() {
+ days++;
+ return *this;
+}
+
+Timespan Timespan::operator++(int) {
+ Timespan old = *this;
+ ++(*this);
+ return old;
+}
+
+Timespan::operator day_t() const {
+ return days;
+}
+
+Timespan::operator double() const {
+ return days;
+}
+
+Timespan::operator std::string() const {
+ return std::to_string(days);
+}
+
+std::ostream& OpenVic::operator<<(std::ostream& out, Timespan const& timespan) {
+ return out << static_cast<std::string>(timespan);
+}
+
+Timespan Date::_dateToTimespan(year_t year, month_t month, day_t day) {
+ month = std::clamp<month_t>(month, 1, MONTHS_IN_YEAR);
+ day = std::clamp<day_t>(day, 1, DAYS_IN_MONTH[month - 1]);
+ return year * DAYS_IN_YEAR + DAYS_UP_TO_MONTH[month - 1] + day - 1;
+}
+
+Timespan::day_t const* Date::DAYS_UP_TO_MONTH = generate_days_up_to_month();
+
+Timespan::day_t const* Date::generate_days_up_to_month() {
+ static Timespan::day_t days_up_to_month[MONTHS_IN_YEAR];
+ Timespan::day_t days = 0;
+ for (int month = 0; month < MONTHS_IN_YEAR;
+ days_up_to_month[month] = days, days += DAYS_IN_MONTH[month++]);
+ assert(days == DAYS_IN_YEAR);
+ return days_up_to_month;
+}
+
+Date::month_t const* Date::MONTH_FROM_DAY_IN_YEAR = generate_month_from_day_in_year();
+
+Date::month_t const* Date::generate_month_from_day_in_year() {
+ static month_t month_from_day_in_year[DAYS_IN_YEAR];
+ Timespan::day_t days_left = 0;
+ for (int day = 0, month = 0; day < DAYS_IN_YEAR;
+ days_left = (days_left > 0 ? days_left : DAYS_IN_MONTH[month++]) - 1,
+ month_from_day_in_year[day++] = month);
+ assert(days_left == 0);
+ assert(month_from_day_in_year[DAYS_IN_YEAR - 1] == MONTHS_IN_YEAR);
+ return month_from_day_in_year;
+}
+
+Date::Date(Timespan total_days) : timespan { total_days } {
+ if (timespan < 0) {
+ Logger::error("Invalid timespan for date: ", timespan, " (cannot be negative)");
+ timespan = 0;
+ }
+}
+
+Date::Date(year_t year, month_t month, day_t day) : timespan { _dateToTimespan(year, month, day) } {}
+
+Date::year_t Date::getYear() const {
+ return static_cast<Timespan::day_t>(timespan) / DAYS_IN_YEAR;
+}
+
+Date::month_t Date::getMonth() const {
+ return MONTH_FROM_DAY_IN_YEAR[static_cast<Timespan::day_t>(timespan) % DAYS_IN_YEAR];
+}
+
+Date::day_t Date::getDay() const {
+ return (static_cast<Timespan::day_t>(timespan) % DAYS_IN_YEAR) - DAYS_UP_TO_MONTH[getMonth() - 1] + 1;
+}
+
+bool Date::operator<(Date other) const { return timespan < other.timespan; };
+bool Date::operator>(Date other) const { return timespan > other.timespan; };
+bool Date::operator<=(Date other) const { return timespan <= other.timespan; };
+bool Date::operator>=(Date other) const { return timespan >= other.timespan; };
+bool Date::operator==(Date other) const { return timespan == other.timespan; };
+bool Date::operator!=(Date other) const { return timespan != other.timespan; };
+
+Date Date::operator+(Timespan other) const { return timespan + other; }
+
+Timespan Date::operator-(Date other) const { return timespan - other.timespan; }
+
+Date& Date::operator+=(Timespan other) {
+ timespan += other;
+ return *this;
+}
+
+Date& Date::operator-=(Timespan other) {
+ timespan -= other;
+ return *this;
+}
+
+Date& Date::operator++() {
+ timespan++;
+ return *this;
+}
+
+Date Date::operator++(int) {
+ Date old = *this;
+ ++(*this);
+ return old;
+}
+
+Date::operator std::string() const {
+ std::stringstream ss;
+ ss << *this;
+ return ss.str();
+}
+
+std::ostream& OpenVic::operator<<(std::ostream& out, Date const& date) {
+ return out << static_cast<int>(date.getYear()) << '.' << static_cast<int>(date.getMonth()) << '.' << static_cast<int>(date.getDay());
+}
+
+// Parsed from string of the form YYYY.MM.DD
+Date Date::from_string(const std::string_view date, bool* successful) {
+ if (successful != nullptr) *successful = true;
+ size_t first_pos = 0;
+ while (first_pos < date.length() && std::isdigit(date[first_pos])) {
+ first_pos++;
+ }
+
+ if (first_pos == 0) {
+ Logger::error("Failed to find year digits in date: ", date);
+ if (successful != nullptr) *successful = false;
+ return {};
+ }
+
+ int val = 0;
+ char const* start = date.data();
+ char const* end = start + first_pos;
+ std::from_chars_result result = std::from_chars(start, end, val);
+ if (result.ec != std::errc{} || result.ptr != end || val < 0 || val >= 1 << (8 * sizeof(year_t))) {
+ Logger::error("Failed to read year: ", date);
+ if (successful != nullptr) *successful = false;
+ return {};
+ }
+ year_t year = val;
+ month_t month = 1;
+ day_t day = 1;
+ if (first_pos < date.length()) {
+ if (date[first_pos] == '.') {
+ size_t second_pos = ++first_pos;
+ while (second_pos < date.length() && std::isdigit(date[second_pos])) {
+ second_pos++;
+ }
+ if (first_pos == second_pos) {
+ Logger::error("Failed to find month digits in date: ", date);
+ if (successful != nullptr) *successful = false;
+ } else {
+ start = date.data() + first_pos;
+ end = date.data() + second_pos;
+ result = std::from_chars(start, end, val);
+ if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > MONTHS_IN_YEAR) {
+ Logger::error("Failed to read month: ", date);
+ if (successful != nullptr) *successful = false;
+ } else {
+ month = val;
+ if (second_pos < date.length()) {
+ if (date[second_pos] == '.') {
+ size_t third_pos = ++second_pos;
+ while (third_pos < date.length() && std::isdigit(date[third_pos])) {
+ third_pos++;
+ }
+ if (second_pos == third_pos) {
+ Logger::error("Failed to find day digits in date: ", date);
+ if (successful != nullptr) *successful = false;
+ } else {
+ start = date.data() + second_pos;
+ end = date.data() + third_pos;
+ result = std::from_chars(start, end, val);
+ if (result.ec != std::errc{} || result.ptr != end || val < 1 || val > DAYS_IN_MONTH[month - 1]) {
+ Logger::error("Failed to read day: ", date);
+ if (successful != nullptr) *successful = false;
+ } else {
+ day = val;
+ if (third_pos < date.length()) {
+ Logger::error("Unexpected string \"", date.substr(third_pos), "\" at the end of date ", date);
+ if (successful != nullptr) *successful = false;
+ }
+ }
+ }
+ } else {
+ Logger::error("Unexpected character \"", date[second_pos], "\" in month of date ", date);
+ if (successful != nullptr) *successful = false;
+ }
+ }
+ }
+ }
+ } else {
+ Logger::error("Unexpected character \"", date[first_pos], "\" in year of date ", date);
+ if (successful != nullptr) *successful = false;
+ }
+ }
+ return _dateToTimespan(year, month, day);
+};
diff --git a/src/openvic/types/Date.hpp b/src/openvic/types/Date.hpp
new file mode 100644
index 0000000..ca5645c
--- /dev/null
+++ b/src/openvic/types/Date.hpp
@@ -0,0 +1,90 @@
+#pragma once
+
+#include <cstdint>
+#include <ostream>
+#include <string>
+
+namespace OpenVic {
+ // A relative period between points in time, measured in days
+ struct Timespan {
+ using day_t = int64_t;
+
+ private:
+ day_t days;
+
+ public:
+ Timespan(day_t value = 0);
+
+ bool operator<(Timespan other) const;
+ bool operator>(Timespan other) const;
+ bool operator<=(Timespan other) const;
+ bool operator>=(Timespan other) const;
+ bool operator==(Timespan other) const;
+ bool operator!=(Timespan other) const;
+
+ Timespan operator+(Timespan other) const;
+ Timespan operator-(Timespan other) const;
+ Timespan operator*(day_t factor) const;
+ Timespan operator/(day_t factor) const;
+ Timespan& operator+=(Timespan other);
+ Timespan& operator-=(Timespan other);
+ Timespan& operator++();
+ Timespan operator++(int);
+
+ explicit operator day_t() const;
+ explicit operator double() const;
+ explicit operator std::string() const;
+ };
+ std::ostream& operator<<(std::ostream& out, Timespan const& timespan);
+
+ // Represents an in-game date
+ // Note: Current implementation does not account for leap-years, or dates before Year 0
+ struct Date {
+ using year_t = uint16_t;
+ using month_t = uint8_t;
+ using day_t = uint8_t;
+
+ static constexpr Timespan::day_t MONTHS_IN_YEAR = 12;
+ static constexpr Timespan::day_t DAYS_IN_YEAR = 365;
+ static constexpr Timespan::day_t DAYS_IN_MONTH[MONTHS_IN_YEAR] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ static Timespan::day_t const* DAYS_UP_TO_MONTH;
+ static month_t const* MONTH_FROM_DAY_IN_YEAR;
+
+ private:
+ // Number of days since Jan 1st, Year 0
+ Timespan timespan;
+
+ static Timespan _dateToTimespan(year_t year, month_t month, day_t day);
+ static Timespan::day_t const* generate_days_up_to_month();
+ static month_t const* generate_month_from_day_in_year();
+
+ public:
+ // The Timespan is considered to be the number of days since Jan 1st, Year 0
+ Date(Timespan total_days);
+ // Year month day specification
+ Date(year_t year = 0, month_t month = 1, day_t day = 1);
+
+ year_t getYear() const;
+ month_t getMonth() const;
+ day_t getDay() const;
+
+ bool operator<(Date other) const;
+ bool operator>(Date other) const;
+ bool operator<=(Date other) const;
+ bool operator>=(Date other) const;
+ bool operator==(Date other) const;
+ bool operator!=(Date other) const;
+
+ Date operator+(Timespan other) const;
+ Timespan operator-(Date other) const;
+ Date& operator+=(Timespan other);
+ Date& operator-=(Timespan other);
+ Date& operator++();
+ Date operator++(int);
+
+ explicit operator std::string() const;
+ // Parsed from string of the form YYYY.MM.DD
+ static Date from_string(const std::string_view date, bool* successful = nullptr);
+ };
+ std::ostream& operator<<(std::ostream& out, Date const& date);
+}
diff --git a/src/openvic/types/IdentifierRegistry.cpp b/src/openvic/types/IdentifierRegistry.cpp
new file mode 100644
index 0000000..97a1ff5
--- /dev/null
+++ b/src/openvic/types/IdentifierRegistry.cpp
@@ -0,0 +1,37 @@
+#include "IdentifierRegistry.hpp"
+
+#include <cassert>
+#include <iomanip>
+#include <sstream>
+
+using namespace OpenVic;
+
+HasIdentifier::HasIdentifier(const std::string_view new_identifier)
+ : identifier { new_identifier } {
+ assert(!identifier.empty());
+}
+
+std::string const& HasIdentifier::get_identifier() const {
+ return identifier;
+}
+
+HasColour::HasColour(colour_t const new_colour, bool can_be_null) : colour(new_colour) {
+ assert((can_be_null || colour != NULL_COLOUR) && colour <= MAX_COLOUR_RGB);
+}
+
+colour_t HasColour::get_colour() const { return colour; }
+
+std::string HasColour::colour_to_hex_string(colour_t const colour) {
+ std::ostringstream stream;
+ stream << std::hex << std::setfill('0') << std::setw(6) << colour;
+ return stream.str();
+}
+
+std::string HasColour::colour_to_hex_string() const {
+ return colour_to_hex_string(colour);
+}
+
+HasIdentifierAndColour::HasIdentifierAndColour(const std::string_view new_identifier,
+ const colour_t new_colour, bool can_be_null)
+ : HasIdentifier { new_identifier },
+ HasColour { new_colour, can_be_null } {}
diff --git a/src/openvic/types/IdentifierRegistry.hpp b/src/openvic/types/IdentifierRegistry.hpp
new file mode 100644
index 0000000..e5ac94b
--- /dev/null
+++ b/src/openvic/types/IdentifierRegistry.hpp
@@ -0,0 +1,142 @@
+#pragma once
+
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+#include "openvic/types/Colour.hpp"
+#include "openvic/types/Return.hpp"
+#include "openvic/utility/Logger.hpp"
+
+namespace OpenVic {
+ /*
+ * Base class for objects with a non-empty string identifier,
+ * uniquely named instances of which can be entered into an
+ * IdentifierRegistry instance.
+ */
+ class HasIdentifier {
+ const std::string identifier;
+
+ protected:
+ HasIdentifier(const std::string_view new_identifier);
+
+ public:
+ HasIdentifier(HasIdentifier const&) = delete;
+ HasIdentifier(HasIdentifier&&) = default;
+ HasIdentifier& operator=(HasIdentifier const&) = delete;
+ HasIdentifier& operator=(HasIdentifier&&) = delete;
+
+ std::string const& get_identifier() const;
+ };
+
+ /*
+ * Base class for objects with associated colour information.
+ */
+ class HasColour {
+ const colour_t colour;
+
+ protected:
+ HasColour(const colour_t new_colour, bool can_be_null);
+
+ public:
+ HasColour(HasColour const&) = delete;
+ HasColour(HasColour&&) = default;
+ HasColour& operator=(HasColour const&) = delete;
+ HasColour& operator=(HasColour&&) = delete;
+
+ colour_t get_colour() const;
+ std::string colour_to_hex_string() const;
+ static std::string colour_to_hex_string(colour_t const colour);
+ };
+
+ /*
+ * Base class for objects with a unique string identifier
+ * and associated colour information.
+ */
+ class HasIdentifierAndColour : public HasIdentifier, public HasColour {
+ protected:
+ HasIdentifierAndColour(const std::string_view new_identifier, const colour_t new_colour, bool can_be_null);
+
+ public:
+ HasIdentifierAndColour(HasIdentifierAndColour const&) = delete;
+ HasIdentifierAndColour(HasIdentifierAndColour&&) = default;
+ HasIdentifierAndColour& operator=(HasIdentifierAndColour const&) = delete;
+ HasIdentifierAndColour& operator=(HasIdentifierAndColour&&) = delete;
+ };
+
+ using distribution_t = std::unordered_map<HasIdentifierAndColour const*, float>;
+
+ /*
+ * Template for a list of objects with unique string identifiers that can
+ * be locked to prevent any further additions. The template argument T is
+ * the type of object that the registry will store, and the second part ensures
+ * that HasIdentifier is a base class of T.
+ */
+ template<class T, typename std::enable_if<std::is_base_of<HasIdentifier, T>::value>::type* = nullptr>
+ class IdentifierRegistry {
+ using identifier_index_map_t = std::map<std::string, size_t, std::less<void>>;
+
+ const std::string name;
+ std::vector<T> items;
+ bool locked = false;
+ identifier_index_map_t identifier_index_map;
+
+ public:
+ IdentifierRegistry(const std::string_view new_name) : name { new_name } {}
+ return_t add_item(T&& item) {
+ if (locked) {
+ Logger::error("Cannot add item to the ", name, " registry - locked!");
+ return FAILURE;
+ }
+ T const* old_item = get_item_by_identifier(item.get_identifier());
+ if (old_item != nullptr) {
+ Logger::error("Cannot add item to the ", name, " registry - an item with the identifier \"", item.get_identifier(), "\" already exists!");
+ return FAILURE;
+ }
+ identifier_index_map[item.get_identifier()] = items.size();
+ items.push_back(std::move(item));
+ return SUCCESS;
+ }
+ void lock(bool log = true) {
+ if (locked) {
+ Logger::error("Failed to lock ", name, " registry - already locked!");
+ } else {
+ locked = true;
+ if (log) Logger::info("Locked ", name, " registry after registering ", get_item_count(), " items");
+ }
+ }
+ bool is_locked() const {
+ return locked;
+ }
+ void reset() {
+ identifier_index_map.clear();
+ items.clear();
+ locked = false;
+ }
+ size_t get_item_count() const {
+ return items.size();
+ }
+ T* get_item_by_identifier(const std::string_view identifier) {
+ const identifier_index_map_t::const_iterator it = identifier_index_map.find(identifier);
+ if (it != identifier_index_map.end()) return &items[it->second];
+ return nullptr;
+ }
+ T const* get_item_by_identifier(const std::string_view identifier) const {
+ const identifier_index_map_t::const_iterator it = identifier_index_map.find(identifier);
+ if (it != identifier_index_map.end()) return &items[it->second];
+ return nullptr;
+ }
+ T* get_item_by_index(size_t index) {
+ return index < items.size() ? &items[index] : nullptr;
+ }
+ T const* get_item_by_index(size_t index) const {
+ return index < items.size() ? &items[index] : nullptr;
+ }
+ std::vector<T>& get_items() {
+ return items;
+ }
+ std::vector<T> const& get_items() const {
+ return items;
+ }
+ };
+}
diff --git a/src/openvic/types/Return.hpp b/src/openvic/types/Return.hpp
new file mode 100644
index 0000000..04c3734
--- /dev/null
+++ b/src/openvic/types/Return.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+namespace OpenVic {
+ using return_t = bool;
+
+ // This mirrors godot::Error, where `OK = 0` and `FAILED = 1`.
+ constexpr return_t SUCCESS = false, FAILURE = true;
+}
diff --git a/src/openvic/types/fixed_point/FP.hpp b/src/openvic/types/fixed_point/FP.hpp
new file mode 100644
index 0000000..8b32906
--- /dev/null
+++ b/src/openvic/types/fixed_point/FP.hpp
@@ -0,0 +1,563 @@
+#pragma once
+
+#include <cstdint>
+#include <cstdlib>
+#include <cerrno>
+#include <cmath>
+#include <limits>
+#include <cstring>
+
+#include "FPLUT.hpp"
+#include "openvic/utility/FloatUtils.hpp"
+#include "openvic/utility/StringUtils.hpp"
+#include "openvic/utility/Logger.hpp"
+
+namespace OpenVic {
+ struct FP {
+ static constexpr size_t SIZE = 8;
+
+ constexpr FP() : value { 0 } {}
+ constexpr FP(int64_t new_value) : value { new_value } {}
+ constexpr FP(int32_t new_value) : value { static_cast<int64_t>(new_value) << FPLUT::PRECISION } {}
+
+ // Trivial destructor
+ ~FP() = default;
+
+ static constexpr FP max() {
+ return std::numeric_limits<int64_t>::max();
+ }
+
+ static constexpr FP min() {
+ return std::numeric_limits<int64_t>::min();
+ }
+
+ static constexpr FP usable_max() {
+ return static_cast<int64_t>(2147483648LL);
+ }
+
+ static constexpr FP usable_min() {
+ return -usable_max();
+ }
+
+ static constexpr FP _0() {
+ return 0;
+ }
+
+ static constexpr FP _1() {
+ return 1;
+ }
+
+ static constexpr FP _2() {
+ return 2;
+ }
+
+ static constexpr FP _3() {
+ return 3;
+ }
+
+ static constexpr FP _4() {
+ return 4;
+ }
+
+ static constexpr FP _5() {
+ return 5;
+ }
+
+ static constexpr FP _6() {
+ return 6;
+ }
+
+ static constexpr FP _7() {
+ return 7;
+ }
+
+ static constexpr FP _8() {
+ return 8;
+ }
+
+ static constexpr FP _9() {
+ return 9;
+ }
+
+ static constexpr FP _10() {
+ return 10;
+ }
+
+ static constexpr FP _50() {
+ return 50;
+ }
+
+ static constexpr FP _100() {
+ return 100;
+ }
+
+ static constexpr FP _200() {
+ return 200;
+ }
+
+ static constexpr FP _0_01() {
+ return _1() / _100();
+ }
+
+ static constexpr FP _0_02() {
+ return _0_01() * 2;
+ }
+
+ static constexpr FP _0_03() {
+ return _0_01() * 3;
+ }
+
+ static constexpr FP _0_04() {
+ return _0_01() * 4;
+ }
+
+ static constexpr FP _0_05() {
+ return _0_01() * 5;
+ }
+
+ static constexpr FP _0_10() {
+ return _1() / 10;
+ }
+
+ static constexpr FP _0_20() {
+ return _0_10() * 2;
+ }
+
+ static constexpr FP _0_25() {
+ return _1() / 4;
+ }
+
+ static constexpr FP _0_33() {
+ return _1() / 3;
+ }
+
+ static constexpr FP _0_50() {
+ return _1() / 2;
+ }
+
+ static constexpr FP _0_75() {
+ return _1() - _0_25();
+ }
+
+ static constexpr FP _0_95() {
+ return _1() - _0_05();
+ }
+
+ static constexpr FP _0_99() {
+ return _1() - _0_01();
+ }
+
+ static constexpr FP _1_01() {
+ return _1() + _0_01();
+ }
+
+ static constexpr FP _1_10() {
+ return _1() + _0_10();
+ }
+
+ static constexpr FP _1_50() {
+ return _1() + _0_50();
+ }
+
+ static constexpr FP minus_one() {
+ return -1;
+ }
+
+ static constexpr FP pi() {
+ return static_cast<int64_t>(205887LL);
+ }
+
+ static constexpr FP pi2() {
+ return pi() * 2;
+ }
+
+ static constexpr FP pi_quarter() {
+ return pi() / 4;
+ }
+
+ static constexpr FP pi_half() {
+ return pi() / 2;
+ }
+
+ static constexpr FP one_div_pi2() {
+ return 1 / pi2();
+ }
+
+ static constexpr FP deg2rad() {
+ return static_cast<int64_t>(1143LL);
+ }
+
+ static constexpr FP rad2deg() {
+ return static_cast<int64_t>(3754936LL);
+ }
+
+ static constexpr FP e() {
+ return static_cast<int64_t>(178145LL);
+ }
+
+ constexpr FP abs() const {
+ return value >= 0 ? value : -value;
+ }
+
+ // Not including sign, so -1.1 -> 0.1
+ constexpr FP get_frac() const {
+ return abs().value & ((1 << FPLUT::PRECISION) - 1);
+ }
+
+ constexpr int64_t to_int64_t() const {
+ return value >> FPLUT::PRECISION;
+ }
+
+ constexpr void set_raw_value(int64_t value) {
+ this->value = value;
+ }
+
+ constexpr int64_t get_raw_value() const {
+ return value;
+ }
+
+ constexpr int32_t to_int32_t() const {
+ return static_cast<int32_t>(value >> FPLUT::PRECISION);
+ }
+
+ constexpr float to_float() const {
+ return value / 65536.0F;
+ }
+
+ constexpr float to_float_rounded() const {
+ return static_cast<float>(FloatUtils::round_to_int64((value / 65536.0F) * 100000.0F)) / 100000.0F;
+ }
+
+ constexpr float to_double() const {
+ return value / 65536.0;
+ }
+
+ constexpr float to_double_rounded() const {
+ return FloatUtils::round_to_int64((value / 65536.0) * 100000.0) / 100000.0;
+ }
+
+ std::string to_string() const {
+ std::string str = std::to_string(to_int64_t()) + ".";
+ FP frac_part = get_frac();
+ do {
+ frac_part.value *= 10;
+ str += std::to_string(frac_part.to_int64_t());
+ frac_part = frac_part.get_frac();
+ } while (frac_part > 0);
+ return str;
+ }
+
+ // Deterministic
+ static constexpr FP parse_raw(int64_t value) {
+ return value;
+ }
+
+ // Deterministic
+ static constexpr FP parse(int64_t value) {
+ return value << FPLUT::PRECISION;
+ }
+
+ // Deterministic
+ static constexpr FP parse(const char* str, const char* end, bool *successful = nullptr) {
+ if (successful != nullptr) *successful = false;
+
+ if (str == nullptr || str >= end) {
+ return _0();
+ }
+
+ bool negative = false;
+
+ if (*str == '-') {
+ negative = true;
+ ++str;
+ if (str == end) return _0();
+ }
+
+ const char* dot_pointer = str;
+ while (*dot_pointer != '.' && ++dot_pointer != end);
+
+ if (dot_pointer == str && dot_pointer + 1 == end) {
+ // Invalid: ".", "+." or "-."
+ return _0();
+ }
+
+ FP result = _0();
+ if (successful != nullptr) *successful = true;
+
+ if (dot_pointer != str) {
+ // Non-empty integer part
+ bool int_successful = false;
+ result += parse_integer(str, dot_pointer, &int_successful);
+ if (!int_successful && successful != nullptr) *successful = false;
+ }
+
+ if (dot_pointer + 1 < end) {
+ // Non-empty fractional part
+ bool frac_successful = false;
+ result += parse_fraction(dot_pointer + 1, end, &frac_successful);
+ if (!frac_successful && successful != nullptr) *successful = false;
+ }
+
+ return negative ? -result : result;
+ }
+
+ static constexpr FP parse(const char* str, size_t length, bool *successful = nullptr) {
+ return parse(str, str + length, successful);
+ }
+
+ // Not Deterministic
+ static constexpr FP parse_unsafe(float value) {
+ return static_cast<int64_t>(value * FPLUT::ONE + 0.5f * (value < 0 ? -1 : 1));
+ }
+
+ // Not Deterministic
+ static FP parse_unsafe(const char* value) {
+ char* endpointer;
+ double double_value = std::strtod(value, &endpointer);
+
+ if (*endpointer != '\0') {
+ Logger::error("Unsafe fixed point parse failed to parse the end of a string: \"", endpointer, "\"");
+ }
+
+ int64_t integer_value = static_cast<long>(double_value * FPLUT::ONE + 0.5 * (double_value < 0 ? -1 : 1));
+
+ return integer_value;
+ }
+
+ constexpr operator int32_t() const {
+ return to_int32_t();
+ }
+
+ constexpr operator int64_t() const {
+ return to_int64_t();
+ }
+
+ constexpr operator float() const {
+ return to_float();
+ }
+
+ constexpr operator double() const {
+ return to_double();
+ }
+
+ operator std::string() const {
+ return to_string();
+ }
+
+ friend std::ostream& operator<<(std::ostream& stream, const FP& obj) {
+ return stream << obj.to_string();
+ }
+
+ constexpr friend FP operator-(const FP& obj) {
+ return -obj.value;
+ }
+
+ constexpr friend FP operator+(const FP& obj) {
+ return +obj.value;
+ }
+
+ constexpr friend FP operator+(const FP& lhs, const FP& rhs) {
+ return lhs.value + rhs.value;
+ }
+
+ constexpr friend FP operator+(const FP& lhs, const int32_t& rhs) {
+ return lhs.value + (static_cast<int64_t>(rhs) << FPLUT::PRECISION);
+ }
+
+ constexpr friend FP operator+(const int32_t& lhs, const FP& rhs) {
+ return (static_cast<int64_t>(lhs) << FPLUT::PRECISION) + rhs.value;
+ }
+
+ constexpr FP operator+=(const FP& obj) {
+ value += obj.value;
+ return *this;
+ }
+
+ constexpr FP operator+=(const int32_t& obj) {
+ value += (static_cast<int64_t>(obj) << FPLUT::PRECISION);
+ return *this;
+ }
+
+ constexpr friend FP operator-(const FP& lhs, const FP& rhs) {
+ return lhs.value - rhs.value;
+ }
+
+ constexpr friend FP operator-(const FP& lhs, const int32_t& rhs) {
+ return lhs.value - (static_cast<int64_t>(rhs) << FPLUT::PRECISION);
+ }
+
+ constexpr friend FP operator-(const int32_t& lhs, const FP& rhs) {
+ return (static_cast<int64_t>(lhs) << FPLUT::PRECISION) - rhs.value;
+ }
+
+ constexpr FP operator-=(const FP& obj) {
+ value -= obj.value;
+ return *this;
+ }
+
+ constexpr FP operator-=(const int32_t& obj) {
+ value -= (static_cast<int64_t>(obj) << FPLUT::PRECISION);
+ return *this;
+ }
+
+ constexpr friend FP operator*(const FP& lhs, const FP& rhs) {
+ return lhs.value * rhs.value >> FPLUT::PRECISION;
+ }
+
+ constexpr friend FP operator*(const FP& lhs, const int32_t& rhs) {
+ return lhs.value * rhs;
+ }
+
+ constexpr friend FP operator*(const int32_t& lhs, const FP& rhs) {
+ return lhs * rhs.value;
+ }
+
+ constexpr FP operator*=(const FP& obj) {
+ value *= obj.value >> FPLUT::PRECISION;
+ return *this;
+ }
+
+ constexpr FP operator*=(const int32_t& obj) {
+ value *= obj;
+ return *this;
+ }
+
+ constexpr friend FP operator/(const FP& lhs, const FP& rhs) {
+ return (lhs.value << FPLUT::PRECISION) / rhs.value;
+ }
+
+ constexpr friend FP operator/(const FP& lhs, const int32_t& rhs) {
+ return lhs.value / rhs;
+ }
+
+ constexpr friend FP operator/(const int32_t& lhs, const FP& rhs) {
+ return (static_cast<int64_t>(lhs) << (2 / FPLUT::PRECISION)) / rhs.value;
+ }
+
+ constexpr FP operator/=(const FP& obj) {
+ value = (value << FPLUT::PRECISION) / obj.value;
+ return *this;
+ }
+
+ constexpr FP operator/=(const int32_t& obj) {
+ value /= obj;
+ return *this;
+ }
+
+ constexpr friend FP operator%(const FP& lhs, const FP& rhs) {
+ return lhs.value % rhs.value;
+ }
+
+ constexpr friend FP operator%(const FP& lhs, const int32_t& rhs) {
+ return lhs.value % (static_cast<int64_t>(rhs) << FPLUT::PRECISION);
+ }
+
+ constexpr friend FP operator%(const int32_t& lhs, const FP& rhs) {
+ return (static_cast<int64_t>(lhs) << FPLUT::PRECISION) % rhs.value;
+ }
+
+ constexpr FP operator%=(const FP& obj) {
+ value %= obj.value;
+ return *this;
+ }
+
+ constexpr FP operator%=(const int32_t& obj) {
+ value %= (static_cast<int64_t>(obj) << FPLUT::PRECISION);
+ return *this;
+ }
+
+ constexpr friend bool operator<(const FP& lhs, const FP& rhs) {
+ return lhs.value < rhs.value;
+ }
+
+ constexpr friend bool operator<(const FP& lhs, const int32_t& rhs) {
+ return lhs.value < static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator<(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION < rhs.value;
+ }
+
+ constexpr friend bool operator<=(const FP& lhs, const FP& rhs) {
+ return lhs.value <= rhs.value;
+ }
+
+ constexpr friend bool operator<=(const FP& lhs, const int32_t& rhs) {
+ return lhs.value <= static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator<=(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION <= rhs.value;
+ }
+
+ constexpr friend bool operator>(const FP& lhs, const FP& rhs) {
+ return lhs.value > rhs.value;
+ }
+
+ constexpr friend bool operator>(const FP& lhs, const int32_t& rhs) {
+ return lhs.value > static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator>(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION > rhs.value;
+ }
+
+ constexpr friend bool operator>=(const FP& lhs, const FP& rhs) {
+ return lhs.value >= rhs.value;
+ }
+
+ constexpr friend bool operator>=(const FP& lhs, const int32_t& rhs) {
+ return lhs.value >= static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator>=(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION >= rhs.value;
+ }
+
+ constexpr friend bool operator==(const FP& lhs, const FP& rhs) {
+ return lhs.value == rhs.value;
+ }
+
+ constexpr friend bool operator==(const FP& lhs, const int32_t& rhs) {
+ return lhs.value == static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator==(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION == rhs.value;
+ }
+
+ constexpr friend bool operator!=(const FP& lhs, const FP& rhs) {
+ return lhs.value != rhs.value;
+ }
+
+ constexpr friend bool operator!=(const FP& lhs, const int32_t& rhs) {
+ return lhs.value != static_cast<int64_t>(rhs) << FPLUT::PRECISION;
+ }
+
+ constexpr friend bool operator!=(const int32_t& lhs, const FP& rhs) {
+ return static_cast<int64_t>(lhs) << FPLUT::PRECISION != rhs.value;
+ }
+
+
+ private:
+ int64_t value;
+
+ static constexpr FP parse_integer(const char* str, const char* end, bool* successful) {
+ int64_t parsed_value = StringUtils::string_to_int64(str, end, successful, 10);
+ return parse(parsed_value);
+ }
+
+ static constexpr FP parse_fraction(const char* str, const char* end, bool* successful) {
+ constexpr size_t READ_SIZE = 5;
+ if (str + READ_SIZE < end) {
+ Logger::error("Fixed point fraction parse will only use the first ", READ_SIZE,
+ " characters of ", std::string_view { str, static_cast<size_t>(end - str) });
+ end = str + READ_SIZE;
+ }
+ int64_t parsed_value = StringUtils::string_to_int64(str, end, successful, 10);
+ return parse_raw(parsed_value * 65536 / 100000);
+ }
+ };
+
+ static_assert(sizeof(FP) == FP::SIZE, "FP is not 8 bytes");
+}
diff --git a/src/openvic/types/fixed_point/FPLUT.hpp b/src/openvic/types/fixed_point/FPLUT.hpp
new file mode 100644
index 0000000..f9b21ab
--- /dev/null
+++ b/src/openvic/types/fixed_point/FPLUT.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <array>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <numbers>
+#include <utility>
+
+#include "FPLUT_sin_512.hpp"
+
+namespace OpenVic::FPLUT {
+ constexpr int32_t PRECISION = 16;
+ constexpr int64_t ONE = 1 << PRECISION;
+
+ // The LUT index is between 0 and 2^16, the index table goes until 512, if we shift by 7 our index will be between 0 and 511
+ constexpr int32_t SHIFT = 16 - 9;
+
+ constexpr int64_t sin(int64_t value) {
+ int sign = 1;
+ if (value < 0) {
+ value = -value;
+ sign = -1;
+ }
+
+ int index = value >> SHIFT;
+ int64_t a = SIN_LUT[index];
+ int64_t b = SIN_LUT[index + 1];
+ int64_t fraction = (value - (index << SHIFT)) << 9;
+ int64_t result = a + (((b - a) * fraction) >> PRECISION);
+ return result * sign;
+ }
+}
diff --git a/src/openvic/types/fixed_point/FPLUT_sin_512.hpp b/src/openvic/types/fixed_point/FPLUT_sin_512.hpp
new file mode 100644
index 0000000..1af0380
--- /dev/null
+++ b/src/openvic/types/fixed_point/FPLUT_sin_512.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <cstdint>
+
+static constexpr int32_t SIN_LUT[] = {
+ 0, 804, 1608, 2412, 3216, 4019, 4821, 5623, 6424, 7224,
+ 8022, 8820, 9616, 10411, 11204, 11996, 12785, 13573, 14359, 15143,
+ 15924, 16703, 17479, 18253, 19024, 19792, 20557, 21320, 22078, 22834,
+ 23586, 24335, 25080, 25821, 26558, 27291, 28020, 28745, 29466, 30182,
+ 30893, 31600, 32303, 33000, 33692, 34380, 35062, 35738, 36410, 37076,
+ 37736, 38391, 39040, 39683, 40320, 40951, 41576, 42194, 42806, 43412,
+ 44011, 44604, 45190, 45769, 46341, 46906, 47464, 48015, 48559, 49095,
+ 49624, 50146, 50660, 51166, 51665, 52156, 52639, 53114, 53581, 54040,
+ 54491, 54934, 55368, 55794, 56212, 56621, 57022, 57414, 57798, 58172,
+ 58538, 58896, 59244, 59583, 59914, 60235, 60547, 60851, 61145, 61429,
+ 61705, 61971, 62228, 62476, 62714, 62943, 63162, 63372, 63572, 63763,
+ 63944, 64115, 64277, 64429, 64571, 64704, 64827, 64940, 65043, 65137,
+ 65220, 65294, 65358, 65413, 65457, 65492, 65516, 65531, 65536, 65531,
+ 65516, 65492, 65457, 65413, 65358, 65294, 65220, 65137, 65043, 64940,
+ 64827, 64704, 64571, 64429, 64277, 64115, 63944, 63763, 63572, 63372,
+ 63162, 62943, 62714, 62476, 62228, 61971, 61705, 61429, 61145, 60851,
+ 60547, 60235, 59914, 59583, 59244, 58896, 58538, 58172, 57798, 57414,
+ 57022, 56621, 56212, 55794, 55368, 54934, 54491, 54040, 53581, 53114,
+ 52639, 52156, 51665, 51166, 50660, 50146, 49624, 49095, 48559, 48015,
+ 47464, 46906, 46341, 45769, 45190, 44604, 44011, 43412, 42806, 42194,
+ 41576, 40951, 40320, 39683, 39040, 38391, 37736, 37076, 36410, 35738,
+ 35062, 34380, 33692, 33000, 32303, 31600, 30893, 30182, 29466, 28745,
+ 28020, 27291, 26558, 25821, 25080, 24335, 23586, 22834, 22078, 21320,
+ 20557, 19792, 19024, 18253, 17479, 16703, 15924, 15143, 14359, 13573,
+ 12785, 11996, 11204, 10411, 9616, 8820, 8022, 7224, 6424, 5623,
+ 4821, 4019, 3216, 2412, 1608, 804, 0, -804, -1608, -2412,
+ -3216, -4019, -4821, -5623, -6424, -7224, -8022, -8820, -9616, -10411,
+ -11204, -11996, -12785, -13573, -14359, -15143, -15924, -16703, -17479, -18253,
+ -19024, -19792, -20557, -21320, -22078, -22834, -23586, -24335, -25080, -25821,
+ -26558, -27291, -28020, -28745, -29466, -30182, -30893, -31600, -32303, -33000,
+ -33692, -34380, -35062, -35738, -36410, -37076, -37736, -38391, -39040, -39683,
+ -40320, -40951, -41576, -42194, -42806, -43412, -44011, -44604, -45190, -45769,
+ -46341, -46906, -47464, -48015, -48559, -49095, -49624, -50146, -50660, -51166,
+ -51665, -52156, -52639, -53114, -53581, -54040, -54491, -54934, -55368, -55794,
+ -56212, -56621, -57022, -57414, -57798, -58172, -58538, -58896, -59244, -59583,
+ -59914, -60235, -60547, -60851, -61145, -61429, -61705, -61971, -62228, -62476,
+ -62714, -62943, -63162, -63372, -63572, -63763, -63944, -64115, -64277, -64429,
+ -64571, -64704, -64827, -64940, -65043, -65137, -65220, -65294, -65358, -65413,
+ -65457, -65492, -65516, -65531, -65536, -65531, -65516, -65492, -65457, -65413,
+ -65358, -65294, -65220, -65137, -65043, -64940, -64827, -64704, -64571, -64429,
+ -64277, -64115, -63944, -63763, -63572, -63372, -63162, -62943, -62714, -62476,
+ -62228, -61971, -61705, -61429, -61145, -60851, -60547, -60235, -59914, -59583,
+ -59244, -58896, -58538, -58172, -57798, -57414, -57022, -56621, -56212, -55794,
+ -55368, -54934, -54491, -54040, -53581, -53114, -52639, -52156, -51665, -51166,
+ -50660, -50146, -49624, -49095, -48559, -48015, -47464, -46906, -46341, -45769,
+ -45190, -44604, -44011, -43412, -42806, -42194, -41576, -40951, -40320, -39683,
+ -39040, -38391, -37736, -37076, -36410, -35738, -35062, -34380, -33692, -33000,
+ -32303, -31600, -30893, -30182, -29466, -28745, -28020, -27291, -26558, -25821,
+ -25080, -24335, -23586, -22834, -22078, -21320, -20557, -19792, -19024, -18253,
+ -17479, -16703, -15924, -15143, -14359, -13573, -12785, -11996, -11204, -10411,
+ -9616, -8820, -8022, -7224, -6424, -5623, -4821, -4019, -3216, -2412,
+ -1608, -804, 0
+};
diff --git a/src/openvic/types/fixed_point/FPMath.hpp b/src/openvic/types/fixed_point/FPMath.hpp
new file mode 100644
index 0000000..965d63c
--- /dev/null
+++ b/src/openvic/types/fixed_point/FPMath.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "FP.hpp"
+
+namespace OpenVic::FPMath {
+ constexpr FP sin(FP number) {
+ number %= FP::pi2();
+ number *= FP::one_div_pi2();
+ return FPLUT::sin(number.get_raw_value());
+ }
+}
diff --git a/src/openvic/types/fixed_point/lut_generator/lut_generator.py b/src/openvic/types/fixed_point/lut_generator/lut_generator.py
new file mode 100644
index 0000000..5c5ba8b
--- /dev/null
+++ b/src/openvic/types/fixed_point/lut_generator/lut_generator.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+import math
+
+PRECISION = 16
+ONE = 1 << PRECISION
+SIN_VALUE_COUNT = 512
+
+SinLut = []
+
+for i in range(SIN_VALUE_COUNT):
+ angle = 2 * math.pi * i / SIN_VALUE_COUNT
+
+ sin_value = math.sin(angle)
+ moved_sin = sin_value * ONE
+ rounded_sin = int(moved_sin + 0.5) if moved_sin > 0 else int(moved_sin - 0.5)
+ SinLut.append(rounded_sin)
+
+SinLut.append(SinLut[0])
+
+output = [
+"""#pragma once
+
+#include <cstdint>
+
+static constexpr int32_t SIN_LUT[] = {"""
+]
+
+lines = [SinLut[i:i+10] for i in range(0, len(SinLut), 10)]
+
+for line in lines:
+ output.append("\t" + ", ".join(str(value) for value in line) + ",")
+
+output[-1] = output[-1][:-1] # Remove the last comma
+output.append("};\n")
+
+cpp_code = "\n".join(output)
+
+with open(f"FPLUT_sin_{SIN_VALUE_COUNT}.hpp", "w", newline="\n") as file:
+ file.write(cpp_code)