Initial Commit
This commit is contained in:
commit
6088fc29ed
978 changed files with 353647 additions and 0 deletions
96
include/Helpers/BezierEasing.cpp
Normal file
96
include/Helpers/BezierEasing.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// The cubic bezier easing implementation is a C++ version of the following javascript code:
|
||||
/**
|
||||
* https://github.com/gre/bezier-easing
|
||||
* BezierEasing - use bezier curve for transition easing function
|
||||
* by Gaëtan Renaudeau 2014 - 2015 – MIT License
|
||||
*/
|
||||
#include "BezierEasing.hpp"
|
||||
#include <cmath>
|
||||
|
||||
BezierEasing BezierEasing::linear{0.0, 1.0, 0.0, 1.0};
|
||||
|
||||
BezierEasing::BezierEasing(Eigen::Vector4f c):
|
||||
BezierEasing(c.x(), c.y(), c.z(), c.w()) {}
|
||||
|
||||
BezierEasing::BezierEasing(float x1, float y1, float x2, float y2):
|
||||
mX1(x1),
|
||||
mY1(y1),
|
||||
mX2(x2),
|
||||
mY2(y2)
|
||||
{
|
||||
for(size_t i = 0; i < KSPLINE_TABLE_SIZE; ++i)
|
||||
sampleValues[i] = calc_bezier(i * KSAMPLE_STEP_SIZE, mX1, mX2);
|
||||
}
|
||||
|
||||
float BezierEasing::A(float aA1, float aA2) const {
|
||||
return 1.0 - 3.0 * aA2 + 3.0 * aA1;
|
||||
}
|
||||
|
||||
float BezierEasing::B(float aA1, float aA2) const {
|
||||
return 3.0 * aA2 - 6.0 * aA1;
|
||||
}
|
||||
|
||||
float BezierEasing::C(float aA1) const {
|
||||
return 3.0 * aA1;
|
||||
}
|
||||
|
||||
float BezierEasing::calc_bezier(float aT, float aA1, float aA2) const {
|
||||
return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
|
||||
}
|
||||
|
||||
float BezierEasing::get_slope(float aT, float aA1, float aA2) const {
|
||||
return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
|
||||
}
|
||||
|
||||
float BezierEasing::binary_subdivide(float aX, float aA, float aB) const {
|
||||
float currentX = 0;
|
||||
float currentT = 0;
|
||||
unsigned i = 0;
|
||||
do {
|
||||
currentT = aA + (aB - aA) / 2;
|
||||
currentX = calc_bezier(currentT, mX1, mX2) - aX;
|
||||
if(currentX > 0)
|
||||
aB = currentT;
|
||||
else
|
||||
aA = currentT;
|
||||
} while(std::abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
|
||||
return currentT;
|
||||
}
|
||||
|
||||
float BezierEasing::newton_raphson_iterate(float aX, float aGuessT) const {
|
||||
for(unsigned i = 0; i < NEWTON_ITERATIONS; i++) {
|
||||
float currentSlope = get_slope(aGuessT, mX1, mX2);
|
||||
if(currentSlope == 0)
|
||||
return aGuessT;
|
||||
float currentX = calc_bezier(aGuessT, mX1, mX2) - aX;
|
||||
aGuessT -= currentX / currentSlope;
|
||||
}
|
||||
return aGuessT;
|
||||
}
|
||||
|
||||
float BezierEasing::get_t_for_x(float aX) const {
|
||||
float intervalStart = 0.0;
|
||||
unsigned currentSample = 1;
|
||||
|
||||
for(; currentSample != KSPLINE_TABLE_LAST && sampleValues[currentSample] <= aX; ++currentSample)
|
||||
intervalStart += KSAMPLE_STEP_SIZE;
|
||||
--currentSample;
|
||||
|
||||
float dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
|
||||
float guessForT = intervalStart + dist * KSAMPLE_STEP_SIZE;
|
||||
float initialSlope = get_slope(guessForT, mX1, mX2);
|
||||
|
||||
if(initialSlope >= NEWTON_MIN_SLOPE)
|
||||
return newton_raphson_iterate(aX, guessForT);
|
||||
else if(initialSlope == 0.0)
|
||||
return guessForT;
|
||||
else
|
||||
return binary_subdivide(aX, intervalStart, intervalStart + KSAMPLE_STEP_SIZE);
|
||||
}
|
||||
|
||||
float BezierEasing::operator() (float x) const {
|
||||
if(x == 0.0 || x == 1.0 || (mX1 == mY1 && mX2 == mY2))
|
||||
return x;
|
||||
|
||||
return calc_bezier(get_t_for_x(x), mY1, mY2);
|
||||
}
|
||||
42
include/Helpers/BezierEasing.hpp
Normal file
42
include/Helpers/BezierEasing.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// The cubic bezier easing implementation is a C++ version of the following javascript code:
|
||||
/**
|
||||
* https://github.com/gre/bezier-easing
|
||||
* BezierEasing - use bezier curve for transition easing function
|
||||
* by Gaëtan Renaudeau 2014 - 2015 – MIT License
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <Eigen/Dense>
|
||||
|
||||
class BezierEasing {
|
||||
public:
|
||||
BezierEasing(Eigen::Vector4f c);
|
||||
BezierEasing(float x1, float y1, float x2, float y2);
|
||||
float operator() (float x) const;
|
||||
static BezierEasing linear;
|
||||
private:
|
||||
static constexpr unsigned NEWTON_ITERATIONS = 4;
|
||||
static constexpr float NEWTON_MIN_SLOPE = 0.001;
|
||||
static constexpr float SUBDIVISION_PRECISION = 0.0000001;
|
||||
static constexpr unsigned SUBDIVISION_MAX_ITERATIONS = 10;
|
||||
static constexpr unsigned KSPLINE_TABLE_SIZE = 11;
|
||||
static constexpr unsigned KSPLINE_TABLE_LAST = KSPLINE_TABLE_SIZE - 1;
|
||||
static constexpr float KSAMPLE_STEP_SIZE = 1.0 / (KSPLINE_TABLE_SIZE - 1.0);
|
||||
|
||||
float A(float aA1, float aA2) const;
|
||||
float B(float aA1, float aA2) const;
|
||||
float C(float aA1) const;
|
||||
float calc_bezier(float aT, float aA1, float aA2) const;
|
||||
float get_slope(float aT, float aA1, float aA2) const;
|
||||
float binary_subdivide(float aX, float aA, float aB) const;
|
||||
float newton_raphson_iterate(float aX, float aGuessT) const;
|
||||
float get_t_for_x(float aX) const;
|
||||
|
||||
float mX1;
|
||||
float mY1;
|
||||
float mX2;
|
||||
float mY2;
|
||||
|
||||
std::array<float, KSPLINE_TABLE_SIZE> sampleValues;
|
||||
};
|
||||
29
include/Helpers/ConvertVec.hpp
Normal file
29
include/Helpers/ConvertVec.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#include <clay.h>
|
||||
|
||||
#ifdef IS_CLIENT
|
||||
#include <include/core/SkPoint.h>
|
||||
template <typename S> S convert_vec2(const SkPoint& t) {
|
||||
return S{t.fX, t.fY};
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename S, typename T> S convert_vec2(const T& t) {
|
||||
return S{t[0], t[1]};
|
||||
}
|
||||
|
||||
template <typename S, typename T> S convert_vec3(const T& t) {
|
||||
return S{t[0], t[1], t[2]};
|
||||
}
|
||||
|
||||
template <typename S, typename T> S convert_vec4(const T& t) {
|
||||
return S{t[0], t[1], t[2], t[3]};
|
||||
}
|
||||
|
||||
template <typename S> S convert_vec4(const Clay_Color& t) {
|
||||
return S{t.r, t.g, t.b, t.a};
|
||||
}
|
||||
|
||||
template <typename S> S convert_vec2(const Clay_Dimensions& t) {
|
||||
return S{t.width, t.height};
|
||||
}
|
||||
346
include/Helpers/FixedPoint.hpp
Normal file
346
include/Helpers/FixedPoint.hpp
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
#pragma once
|
||||
#include "Helpers/StringHelpers.hpp"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <Eigen/Core>
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
namespace FixedPoint {
|
||||
template <typename T> T abs(const T& a) {
|
||||
if(a < T(0))
|
||||
return -a;
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T> T sqrt(const T& a) {
|
||||
return T(boost::multiprecision::sqrt(a.get_underlying_val() << a.bit_fraction_count()), true);
|
||||
}
|
||||
|
||||
template <typename T> T lerp(const T& a, const T& b, const T& t) {
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
template <typename T> T lerp_double(const T& a, const T& b, double t) {
|
||||
if(t == 0.0)
|
||||
return a;
|
||||
else
|
||||
return a + (b - a) / T(1.0 / t);
|
||||
}
|
||||
|
||||
// Gets highest bit in UNDERLYING VALUE, not the integer portion of the number
|
||||
template <typename T> int to_highest_bit(const T& a) {
|
||||
typename T::UnderlyingType underNum = a.get_underlying_val();
|
||||
if(underNum == 0)
|
||||
return 0;
|
||||
|
||||
int b = 0;
|
||||
while(underNum >>= 1)
|
||||
b++;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
// https://github.com/Koishi-Satori/EirinFixed/blob/main/include/eirin/math.hpp
|
||||
template <typename T> T log2(const T& a) {
|
||||
typename T::UnderlyingType b(typename T::UnderlyingType(1u) << (a.bit_fraction_count() - 1));
|
||||
typename T::UnderlyingType y(0);
|
||||
typename T::UnderlyingType x = a.get_underlying_val();
|
||||
|
||||
if(a < T(0))
|
||||
throw std::runtime_error("[FixedPoint::log2] Number outside domain");
|
||||
|
||||
while(x < (typename T::UnderlyingType(1u) << a.bit_fraction_count()))
|
||||
{
|
||||
x <<= 1;
|
||||
y -= (typename T::UnderlyingType(1u)) << a.bit_fraction_count();
|
||||
}
|
||||
|
||||
while(x >= (typename T::UnderlyingType(2u) << a.bit_fraction_count()))
|
||||
{
|
||||
x >>= 1;
|
||||
y += (typename T::UnderlyingType(1u)) << a.bit_fraction_count();
|
||||
}
|
||||
|
||||
typename T::UnderlyingType z = x;
|
||||
for(int i = 0; i < a.bit_fraction_count(); ++i)
|
||||
{
|
||||
z = (z * z) >> a.bit_fraction_count();
|
||||
if(z >= (typename T::UnderlyingType(2u) << a.bit_fraction_count()))
|
||||
{
|
||||
z >>= 1;
|
||||
y += b;
|
||||
}
|
||||
b = b >> 1;
|
||||
}
|
||||
|
||||
return T(y, true);
|
||||
}
|
||||
|
||||
// https://github.com/MikeLankamp/fpm/blob/master/include/fpm/math.hpp
|
||||
template <typename T> T exp2(T x) {
|
||||
if(x < T(0))
|
||||
return T(1) / exp2(-x);
|
||||
|
||||
typename T::UnderlyingType x_int = x.get_underlying_val() >> x.bit_fraction_count();
|
||||
x -= T(x_int, false);
|
||||
assert(x >= T(0) && x < T(1));
|
||||
|
||||
T fA(1.8964611454333148e-3);
|
||||
T fB(8.9428289841091295e-3);
|
||||
T fC(5.5866246304520701e-2);
|
||||
T fD(2.4013971109076949e-1);
|
||||
T fE(6.9315475247516736e-1);
|
||||
T fF(9.9999989311082668e-1);
|
||||
return T(typename T::UnderlyingType(1) << static_cast<int>(x_int), false) * (((((fA * x + fB) * x + fC) * x + fD) * x + fE) * x + fF);
|
||||
}
|
||||
|
||||
template <typename T, int bitFractionCount> class Number {
|
||||
public:
|
||||
typedef T UnderlyingType;
|
||||
|
||||
static int bit_fraction_count() {
|
||||
return bitFractionCount;
|
||||
}
|
||||
|
||||
Number():
|
||||
val(0) {}
|
||||
|
||||
explicit Number(double initVal) {
|
||||
val = T(initVal * std::pow(2, bitFractionCount));
|
||||
}
|
||||
|
||||
explicit Number(float initVal) {
|
||||
val = T(initVal * std::pow(2, bitFractionCount));
|
||||
}
|
||||
|
||||
explicit Number(int64_t initVal) {
|
||||
val = T(initVal) << bitFractionCount;
|
||||
}
|
||||
|
||||
explicit Number(int32_t initVal) {
|
||||
val = T(initVal) << bitFractionCount;
|
||||
}
|
||||
|
||||
explicit Number(int16_t initVal) {
|
||||
val = T(initVal) << bitFractionCount;
|
||||
}
|
||||
|
||||
explicit Number(uint64_t initVal) {
|
||||
val = T(initVal) << bitFractionCount;
|
||||
}
|
||||
|
||||
explicit Number(uint32_t initVal) {
|
||||
val = T(initVal) << bitFractionCount;
|
||||
}
|
||||
|
||||
explicit Number(uint16_t initVal) {
|
||||
val = T(initVal) << bitFractionCount;
|
||||
}
|
||||
|
||||
explicit Number(const std::string& initVal) {
|
||||
val = T(initVal) << bitFractionCount;
|
||||
}
|
||||
|
||||
template <typename Archive> void load(Archive& a) {
|
||||
bool isNegative;
|
||||
std::vector<uint8_t> v;
|
||||
a(isNegative, v);
|
||||
boost::multiprecision::import_bits(val, v.begin(), v.end(), 8, true);
|
||||
if(isNegative)
|
||||
val = -val;
|
||||
}
|
||||
|
||||
template <typename Archive> void save(Archive& a) const {
|
||||
bool isNegative = val < 0;
|
||||
std::vector<uint8_t> v;
|
||||
T absVal = boost::multiprecision::abs(val);
|
||||
boost::multiprecision::export_bits(absVal, std::back_inserter(v), 8, true);
|
||||
a(isNegative, v);
|
||||
}
|
||||
|
||||
Number operator-(const Number& other) const {
|
||||
return Number(val - other.val, true);
|
||||
}
|
||||
|
||||
Number operator-() const {
|
||||
return Number(-val, true);
|
||||
}
|
||||
|
||||
Number operator/(const Number& other) const {
|
||||
return Number((val << bitFractionCount) / other.val, true);
|
||||
}
|
||||
|
||||
Number operator*(const Number& other) const {
|
||||
return Number((val * other.val) >> bitFractionCount, true);
|
||||
}
|
||||
|
||||
template <int otherDecimalCount> Number multiply_different_precision(const Number<T, otherDecimalCount>& other) const {
|
||||
return Number(((val * other.get_underlying_val()) >> other.bit_fraction_count()), true);
|
||||
}
|
||||
|
||||
Number multiply_double(double a) const {
|
||||
if(std::fabs(a) < 1.0)
|
||||
a = 1.0 / a;
|
||||
return (*this) * Number(a);
|
||||
}
|
||||
|
||||
Number divide_double(double a) const {
|
||||
if(std::fabs(a) < 1.0)
|
||||
a = 1.0 / a;
|
||||
return (*this) / Number(a);
|
||||
}
|
||||
|
||||
const T& get_underlying_val() const {
|
||||
return val;
|
||||
}
|
||||
|
||||
Number operator+(const Number& other) const {
|
||||
return Number(val + other.val, true);
|
||||
}
|
||||
|
||||
Number operator%(const Number& other) const {
|
||||
return Number(val % other.val, true);
|
||||
}
|
||||
|
||||
Number& operator+=(const Number& other) {
|
||||
val += other.val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Number& operator-=(const Number& other) {
|
||||
val -= other.val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Number& operator*=(const Number& other) {
|
||||
val *= other.val;
|
||||
val >>= bitFractionCount;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Number& operator/=(const Number& other) {
|
||||
val <<= bitFractionCount;
|
||||
val /= other.val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Number& operator%=(const Number& other) {
|
||||
val %= other.val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator float() const {
|
||||
return static_cast<float>(val) / std::pow(2, bitFractionCount);
|
||||
}
|
||||
|
||||
explicit operator double() const {
|
||||
return static_cast<double>(val) / std::pow(2, bitFractionCount);
|
||||
}
|
||||
|
||||
explicit operator int64_t() const {
|
||||
return static_cast<int64_t>(val >> bitFractionCount);
|
||||
}
|
||||
|
||||
explicit operator uint64_t() const {
|
||||
return static_cast<uint64_t>(val >> bitFractionCount);
|
||||
}
|
||||
|
||||
std::string to_underlying_str() const {
|
||||
return val.str();
|
||||
}
|
||||
|
||||
void from_underlying_str(const std::string& str) {
|
||||
val = T(str);
|
||||
}
|
||||
|
||||
std::string display_int_str(size_t scientificNotationDigits) const {
|
||||
if(scientificNotationDigits <= 0) {
|
||||
T shiftedVal = val >> bitFractionCount;
|
||||
return shiftedVal.str();
|
||||
}
|
||||
else {
|
||||
T shiftedVal = val >> bitFractionCount;
|
||||
std::string a = shiftedVal.str();
|
||||
if(a.size() > (scientificNotationDigits + 2)) {
|
||||
bool isNegative = val < 0;
|
||||
size_t exponent = a.size() - (isNegative ? 2 : 1);
|
||||
a = a.substr(0, scientificNotationDigits + (isNegative ? 2 : 1));
|
||||
a.insert(a.begin() + (isNegative ? 2 : 1), '.');
|
||||
a += "e" + std::to_string(exponent);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, Number const& m) {
|
||||
return os << static_cast<double>(m);
|
||||
}
|
||||
|
||||
bool operator<(const Number& other) const {
|
||||
return val < other.val;
|
||||
}
|
||||
|
||||
bool operator>(const Number& other) const {
|
||||
return val > other.val;
|
||||
}
|
||||
|
||||
bool operator>=(const Number& other) const {
|
||||
return val >= other.val;
|
||||
}
|
||||
|
||||
bool operator<=(const Number& other) const {
|
||||
return val <= other.val;
|
||||
}
|
||||
|
||||
bool operator==(const Number& other) const {
|
||||
return val == other.val;
|
||||
}
|
||||
|
||||
Number operator>>(int shiftNum) const {
|
||||
return Number(val >> shiftNum, true);
|
||||
}
|
||||
|
||||
Number operator<<(int shiftNum) const {
|
||||
return Number(val << shiftNum, true);
|
||||
}
|
||||
|
||||
Number(const T& initVal, bool isUnderlyingVal):
|
||||
val(initVal)
|
||||
{
|
||||
if(isUnderlyingVal)
|
||||
val = initVal;
|
||||
else
|
||||
val = initVal << bitFractionCount;
|
||||
}
|
||||
|
||||
private:
|
||||
T val;
|
||||
};
|
||||
}
|
||||
|
||||
typedef FixedPoint::Number<boost::multiprecision::cpp_int, 32> WorldScalar;
|
||||
|
||||
namespace Eigen {
|
||||
template<> struct NumTraits<WorldScalar> : GenericNumTraits<WorldScalar>
|
||||
{
|
||||
typedef WorldScalar Real;
|
||||
typedef WorldScalar NonInteger;
|
||||
typedef WorldScalar Nested;
|
||||
|
||||
static inline Real epsilon() { return Real(0); }
|
||||
static inline Real dummy_precision() { return Real(0); }
|
||||
static inline int digits10() { return 0; }
|
||||
|
||||
enum {
|
||||
IsInteger = 0,
|
||||
IsSigned = 1,
|
||||
IsComplex = 0,
|
||||
RequireInitialization = 1,
|
||||
ReadCost = 6,
|
||||
AddCost = 150,
|
||||
MulCost = 100
|
||||
};
|
||||
};
|
||||
}
|
||||
5
include/Helpers/Hashes.cpp
Normal file
5
include/Helpers/Hashes.cpp
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#include "Hashes.hpp"
|
||||
|
||||
bool operator <(const Vector2d& a, const Vector2d& b) {
|
||||
return ((a.x() < b.x()) || ((a.x() == b.x()) && (a.y() < b.y())));
|
||||
}
|
||||
64
include/Helpers/Hashes.hpp
Normal file
64
include/Helpers/Hashes.hpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
#include <utility>
|
||||
#include <Eigen/Dense>
|
||||
#include "Serializers.hpp"
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
typedef Vector<uint32_t, 2> Vector2ui32;
|
||||
|
||||
// https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/54728293#54728293
|
||||
|
||||
template <typename T, typename... Rest>
|
||||
void hash_combine(uint64_t& seed, const T& v, const Rest&... rest) {
|
||||
seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
(hash_combine(seed, rest), ...);
|
||||
}
|
||||
|
||||
template <> struct std::hash<Vector2i> {
|
||||
size_t operator()(const Vector2i& v) const
|
||||
{
|
||||
uint64_t h = 0;
|
||||
hash_combine(h, v.x(), v.y());
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct std::hash<Vector2ui32> {
|
||||
size_t operator()(const Vector2ui32& v) const
|
||||
{
|
||||
uint64_t h = 0;
|
||||
hash_combine(h, v.x(), v.y());
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct std::hash<Vector2f> {
|
||||
size_t operator()(const Vector2f& v) const
|
||||
{
|
||||
uint64_t h = 0;
|
||||
hash_combine(h, v.x(), v.y());
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct std::hash<std::array<Vector2f, 2>> {
|
||||
size_t operator()(const std::array<Vector2f, 2>& v) const
|
||||
{
|
||||
uint64_t h = 0;
|
||||
hash_combine(h, v[0], v[1]);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct std::hash<std::array<float, 4>> {
|
||||
size_t operator()(const std::array<float, 4>& v) const
|
||||
{
|
||||
uint64_t h = 0;
|
||||
hash_combine(h, v[0], v[1], v[2], v[3]);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
bool operator <(const Vector2f& a, const Vector2f& b);
|
||||
109
include/Helpers/HsvRgb.hpp
Normal file
109
include/Helpers/HsvRgb.hpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#pragma once
|
||||
#include <cmath>
|
||||
|
||||
// https://stackoverflow.com/a/6930407
|
||||
|
||||
template <typename S, typename T> S rgb_to_hsv(const T& in)
|
||||
{
|
||||
S out;
|
||||
float min, max, delta;
|
||||
|
||||
min = in[0] < in[1] ? in[0] : in[1];
|
||||
min = min < in[2] ? min : in[2];
|
||||
|
||||
max = in[0] > in[1] ? in[0] : in[1];
|
||||
max = max > in[2] ? max : in[2];
|
||||
|
||||
out[2] = max; // v
|
||||
delta = max - min;
|
||||
if(delta < 0.000001f)
|
||||
{
|
||||
out[1] = 0;
|
||||
out[0] = 0; // undefined, maybe nan?
|
||||
return out;
|
||||
}
|
||||
if(max > 0.0f) { // NOTE: if Max is == 0, this divide would cause a crash
|
||||
out[1] = (delta / max); // s
|
||||
} else {
|
||||
// if max is 0, then r = g = b = 0
|
||||
// s = 0, h is undefined
|
||||
out[1] = 0.0f;
|
||||
out[0] = NAN; // its now undefined
|
||||
return out;
|
||||
}
|
||||
if(in[0] >= max) // > is bogus, just keeps compiler happy
|
||||
out[0] = (in[1] - in[2]) / delta; // between yellow & magenta
|
||||
else {
|
||||
if(in[1] >= max)
|
||||
out[0] = 2.0 + ( in[2] - in[0] ) / delta; // between cyan & yellow
|
||||
else
|
||||
out[0] = 4.0 + ( in[0] - in[1] ) / delta; // between magenta & cyan
|
||||
}
|
||||
|
||||
out[0] *= 60.0f; // degrees
|
||||
|
||||
if(out[0] < 0.0f)
|
||||
out[0] += 360.0f;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
template <typename S, typename T> S hsv_to_rgb(const T& in)
|
||||
{
|
||||
float hh, p, q, t, ff;
|
||||
long i;
|
||||
S out;
|
||||
|
||||
if(in[1] <= 0.0f) { // < is bogus, just shuts up warning
|
||||
out[0] = in[2];
|
||||
out[1] = in[2];
|
||||
out[2] = in[2];
|
||||
return out;
|
||||
}
|
||||
hh = in[0];
|
||||
if(hh >= 360.0f)
|
||||
hh = 0.0f;
|
||||
|
||||
hh /= 60.0f;
|
||||
i = (long)hh;
|
||||
ff = hh - i;
|
||||
p = in[2] * (1.0f - in[1]);
|
||||
q = in[2] * (1.0f - (in[1] * ff));
|
||||
t = in[2] * (1.0f - (in[1] * (1.0f - ff)));
|
||||
|
||||
switch(i) {
|
||||
case 0:
|
||||
out[0] = in[2];
|
||||
out[1] = t;
|
||||
out[2] = p;
|
||||
break;
|
||||
case 1:
|
||||
out[0] = q;
|
||||
out[1] = in[2];
|
||||
out[2] = p;
|
||||
break;
|
||||
case 2:
|
||||
out[0] = p;
|
||||
out[1] = in[2];
|
||||
out[2] = t;
|
||||
break;
|
||||
case 3:
|
||||
out[0] = p;
|
||||
out[1] = q;
|
||||
out[2] = in[2];
|
||||
break;
|
||||
case 4:
|
||||
out[0] = t;
|
||||
out[1] = p;
|
||||
out[2] = in[2];
|
||||
break;
|
||||
case 5:
|
||||
default:
|
||||
out[0] = in[2];
|
||||
out[1] = p;
|
||||
out[2] = q;
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
22
include/Helpers/Logger.cpp
Normal file
22
include/Helpers/Logger.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include "Logger.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
Logger Logger::global;
|
||||
|
||||
Logger& Logger::get() {
|
||||
return global;
|
||||
}
|
||||
|
||||
void Logger::add_log(const std::string& log, const std::function<void(const std::string&)> callback) {
|
||||
std::scoped_lock logLock(logMutex);
|
||||
logs[log] = callback;
|
||||
}
|
||||
|
||||
void Logger::log(const std::string& log, const std::string& text) {
|
||||
std::scoped_lock logLock(logMutex);
|
||||
auto it = logs.find(log);
|
||||
if(it == logs.end())
|
||||
throw std::runtime_error("[Logger::log] Log " + log + " does not exist to log text: " + text);
|
||||
else
|
||||
it->second(text);
|
||||
}
|
||||
15
include/Helpers/Logger.hpp
Normal file
15
include/Helpers/Logger.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
static Logger& get();
|
||||
void add_log(const std::string& log, const std::function<void(const std::string&)> callback);
|
||||
void log(const std::string& log, const std::string& text);
|
||||
private:
|
||||
static Logger global;
|
||||
std::mutex logMutex;
|
||||
std::unordered_map<std::string, std::function<void(const std::string&)>> logs;
|
||||
};
|
||||
271
include/Helpers/MathExtras.hpp
Normal file
271
include/Helpers/MathExtras.hpp
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
#pragma once
|
||||
#include <cmath>
|
||||
#include <Eigen/Dense>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include <numbers>
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
template <typename T> T color_mul_alpha(const T& c, float a) {
|
||||
return T{c[0], c[1], c[2], c[3] * a};
|
||||
}
|
||||
|
||||
template <typename T> T circular_mod(T a, T b) {
|
||||
if(a >= 0)
|
||||
return a % b;
|
||||
else
|
||||
return b - (std::abs(a) % b);
|
||||
}
|
||||
|
||||
template <typename T> T circular_fmod(T a, T b) {
|
||||
if(a >= 0)
|
||||
return std::fmod(a, b);
|
||||
else
|
||||
return b - std::fmod(a, b);
|
||||
}
|
||||
|
||||
template <typename T, typename MultipleType> T round_to_multiple(T a, MultipleType multiple) {
|
||||
T toRet = round(a / multiple) * multiple;
|
||||
if((toRet == +0.0 || toRet == -0.0) && std::signbit(toRet))
|
||||
toRet = 0.0;
|
||||
return toRet;
|
||||
}
|
||||
|
||||
template <typename T, typename MultipleType> T round_vec_to_multiple(T a, MultipleType multiple) {
|
||||
for(size_t i = 0; i < static_cast<size_t>(a.size()); i++)
|
||||
a[i] = round_to_multiple(a[i], multiple);
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T> Matrix<T, 2, 1> perpendicular_vec2(const Matrix<T, 2, 1>& vec) {
|
||||
return Matrix<T, 2, 1>{-vec.y(), vec.x()};
|
||||
}
|
||||
|
||||
template <typename T> T dist_point_line_segment(const Matrix<T, 2, 1>& point, const Matrix<T, 2, 1>& lineP1, const Matrix<T, 2, 1>& lineP2) {
|
||||
T lenSqrd = (lineP1 - lineP2).squaredNorm();
|
||||
if(lenSqrd == 0)
|
||||
return (point - lineP1).norm();
|
||||
T t = std::max<T>(0, std::min<T>(1, (point - lineP1).dot(lineP2 - lineP1) / lenSqrd));
|
||||
return (point - (lineP1 + t * (lineP2 - lineP1))).norm();
|
||||
}
|
||||
|
||||
template <typename T> bool collision_circle_line_segment(const Matrix<T, 2, 1>& circleCenter, T circleRadius, const Matrix<T, 2, 1>& lineP1, const Matrix<T, 2, 1>& lineP2) {
|
||||
return dist_point_line_segment(circleCenter, lineP1, lineP2) <= circleRadius;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/53896859
|
||||
template <typename T> bool is_collision_ray_line_segment(const Vector<T, 2>& rayOrigin, const Vector<T, 2>& rayDirection, const Vector<T, 2>& point1, const Vector<T, 2>& point2) {
|
||||
Vector<T, 2> v1 = rayOrigin - point1;
|
||||
Vector<T, 2> v2 = point2 - point1;
|
||||
Vector<T, 2> v3 = Vector<T, 2>{-rayDirection.y(), rayDirection.x()};
|
||||
|
||||
Vector<T, 3> v2three{v2.x(), v2.y(), 0.0};
|
||||
Vector<T, 3> v1three{v1.x(), v1.y(), 0.0};
|
||||
|
||||
T dot = v2.dot(v3);
|
||||
if (std::abs(dot) < 0.000001)
|
||||
return false;
|
||||
|
||||
T t1 = v2three.cross(v1three) / dot;
|
||||
T t2 = v1.dot(v3) / dot;
|
||||
|
||||
return (t1 >= 0.0 && (t2 >= 0.0 && t2 <= 1.0));
|
||||
}
|
||||
|
||||
// https://www.geeksforgeeks.org/program-for-point-of-intersection-of-two-lines/
|
||||
template <typename T> Vector<T, 2> line_line_intersection(const Vector<T, 2>& a, const Vector<T, 2>& b, const Vector<T, 2>& c, const Vector<T, 2>& d) {
|
||||
T a1 = b.y() - a.y();
|
||||
T b1 = a.x() - b.x();
|
||||
T c1 = a1*(a.x()) + b1*(a.y());
|
||||
|
||||
T a2 = d.y() - c.y();
|
||||
T b2 = c.x() - d.x();
|
||||
T c2 = a2*(c.x())+ b2*(c.y());
|
||||
|
||||
T determinant = a1*b2 - a2*b1;
|
||||
|
||||
T x = (b2*c1 - b1*c2)/determinant;
|
||||
T y = (a1*c2 - a2*c1)/determinant;
|
||||
|
||||
if(std::fabs(determinant) < 0.0000001f)
|
||||
return Vector<T, 2>{std::numeric_limits<T>::max(), std::numeric_limits<T>::max()};
|
||||
|
||||
return Vector<T, 2>{x, y};
|
||||
}
|
||||
|
||||
template <typename T> Vector<T, 2> project_point_on_vec(const Vector<T, 2>& point, const Vector<T, 2> unnormalizedVec) {
|
||||
return ((point.dot(unnormalizedVec)) / unnormalizedVec.dot(unnormalizedVec)) * unnormalizedVec;
|
||||
}
|
||||
|
||||
template <typename T> Vector<T, 2> project_point_on_normalized_vec(const Vector<T, 2>& point, const Vector<T, 2> normalizedVec) {
|
||||
return point.dot(normalizedVec) * normalizedVec;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/61342198
|
||||
template <typename T> Vector<T, 2> project_point_on_line(const Vector<T, 2>& p, const Vector<T, 2>& b, const Vector<T, 2>& a) {
|
||||
Vector<T, 2> ap = p - a;
|
||||
Vector<T, 2> ab = b - a;
|
||||
|
||||
T t = ap.dot(ab) / ab.dot(ab);
|
||||
|
||||
return a + t * ab;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
|
||||
template <typename T> Vector<T, 2> line_line_intersection_inaccurate(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c, const Matrix<T, 2, 1>& d) {
|
||||
T x1 = a.x(); T x2 = b.x(); T x3 = c.x(); T x4 = d.x();
|
||||
T y1 = a.y(); T y2 = b.y(); T y3 = c.y(); T y4 = d.y();
|
||||
T p1 = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
|
||||
T p2 = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
|
||||
return Vector<T, 2>{p1, p2};
|
||||
}
|
||||
|
||||
// https://www.reddit.com/r/algorithms/comments/9moad4/what_is_the_simplest_to_implement_line_segment/
|
||||
// Taken from https://github.com/vlecomte/cp-geo
|
||||
template <typename T> T cross_vals_vec(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b) {
|
||||
return a.x() * b.y() - a.y() * b.x();
|
||||
}
|
||||
|
||||
template <typename T> T orient_vals_vec(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c) {
|
||||
return cross_vals_vec((b - a).eval(), (c - a).eval());
|
||||
}
|
||||
|
||||
template <typename T> bool is_collision_line_segment_line_segment(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c, const Matrix<T, 2, 1>& d) {
|
||||
T oa = orient_vals_vec(c, d, a);
|
||||
T ob = orient_vals_vec(c, d, b);
|
||||
T oc = orient_vals_vec(a, b, c);
|
||||
T od = orient_vals_vec(a, b, d);
|
||||
return (oa * ob < T(0) && oc * od < T(0));
|
||||
}
|
||||
|
||||
template <typename T> std::optional<Vector2d> collision_line_segment_line_segment_pos(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c, const Matrix<T, 2, 1>& d) {
|
||||
T oa = orient_vals_vec(c, d, a);
|
||||
T ob = orient_vals_vec(c, d, b);
|
||||
T oc = orient_vals_vec(a, b, c);
|
||||
T od = orient_vals_vec(a, b, d);
|
||||
if(oa * ob < 0 && oc * od < 0)
|
||||
return std::optional<Vector2d>((a * ob - b * oa) / (ob - oa));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename T> bool is_collision_aabb_line_segment(const AlignedBox<T, 2>& aabb, const Matrix<T, 2, 1>& lineP1, const Matrix<T, 2, 1>& lineP2) {
|
||||
return aabb.contains(lineP1) ||
|
||||
aabb.contains(lineP2) ||
|
||||
is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopLeft), aabb.corner(AlignedBox<T, 2>::TopRight)) ||
|
||||
is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopRight), aabb.corner(AlignedBox<T, 2>::BottomRight)) ||
|
||||
is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::BottomLeft), aabb.corner(AlignedBox<T, 2>::BottomRight)) ||
|
||||
is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopLeft), aabb.corner(AlignedBox<T, 2>::BottomLeft));
|
||||
}
|
||||
|
||||
template <typename T> T vec_distance(const Vector<T, 2>& v1, const Vector<T, 2>& v2) {
|
||||
return (v1 - v2).norm();
|
||||
}
|
||||
|
||||
template <typename T> T vec_length(const Vector<T, 2>& v) {
|
||||
return v.norm();
|
||||
}
|
||||
|
||||
template <typename T> T cwise_vec_min(const T& v1, const T& v2) {
|
||||
T toRet;
|
||||
for(unsigned i = 0; i < v1.size(); i++)
|
||||
toRet[i] = std::min(v1[i], v2[i]);
|
||||
return toRet;
|
||||
}
|
||||
|
||||
template <typename T> T cwise_vec_max(const T& v1, const T& v2) {
|
||||
T toRet;
|
||||
for(unsigned i = 0; i < v1.size(); i++)
|
||||
toRet[i] = std::max(v1[i], v2[i]);
|
||||
return toRet;
|
||||
}
|
||||
|
||||
template <typename T> T cwise_vec_clamp(const T& v1, const T& v2, const T& v3) {
|
||||
T toRet;
|
||||
for(unsigned i = 0; i < v1.size(); i++)
|
||||
toRet[i] = std::clamp(v1[i], v2[i], v3[i]);
|
||||
return toRet;
|
||||
}
|
||||
|
||||
template <typename T> T cwise_vec_modf(const T& v1, T& v2) {
|
||||
T toRet;
|
||||
for(unsigned i = 0; i < v1.size(); i++)
|
||||
toRet[i] = std::modf(v1[i], &v2[i]);
|
||||
return toRet;
|
||||
}
|
||||
|
||||
|
||||
template <typename T> std::vector<Vector<T, 2>> arc_vec(const Vector<T, 2>& arcCenter, const Vector<T, 2>& arcDirStart, const Vector<T, 2>& arcDirEnd, const Vector<T, 2>& arcDirCenter, T arcRadius, unsigned numOfDivisions) {
|
||||
T a1 = std::atan2(arcDirStart.y(), arcDirStart.x());
|
||||
T a2 = std::atan2(arcDirEnd.y(), arcDirEnd.x());
|
||||
T a3 = std::atan2(arcDirCenter.y(), arcDirCenter.x());
|
||||
|
||||
T minAngle = std::min(a1, a2);
|
||||
T maxAngle = std::max(a1, a2);
|
||||
|
||||
bool swapHappen = (a3 > minAngle && a3 > maxAngle) || (a3 < minAngle && a3 < maxAngle);
|
||||
if(swapHappen) {
|
||||
minAngle += 2.0 * std::numbers::pi;
|
||||
std::swap(minAngle, maxAngle);
|
||||
}
|
||||
|
||||
bool flipped = minAngle == a2;
|
||||
|
||||
std::vector<Vector<T, 2>> toRet;
|
||||
T tStep = 1.0 / static_cast<T>(numOfDivisions);
|
||||
if(flipped) {
|
||||
for(unsigned i = numOfDivisions - 1; i > 0; i--) {
|
||||
T newAngle = std::lerp(minAngle, maxAngle, tStep * i);
|
||||
toRet.emplace_back(arcCenter + Vector<T, 2>{std::cos(newAngle), std::sin(newAngle)} * arcRadius);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(unsigned i = 1; i < numOfDivisions; i++) {
|
||||
T newAngle = std::lerp(minAngle, maxAngle, tStep * i);
|
||||
toRet.emplace_back(arcCenter + Vector<T, 2>{std::cos(newAngle), std::sin(newAngle)} * arcRadius);
|
||||
}
|
||||
}
|
||||
return toRet;
|
||||
}
|
||||
|
||||
template <typename T> std::vector<Vector<T, 2>> gen_circle_points(const Vector<T, 2>& center, T radius, unsigned numOfPoints) {
|
||||
std::vector<Vector<T, 2>> toRet;
|
||||
for(unsigned i = 0; i < numOfPoints; i++) {
|
||||
T angle = (static_cast<T>(i) / static_cast<T>(numOfPoints)) * std::numbers::pi * 2;
|
||||
toRet.emplace_back(std::cos(angle) * radius + center.x(), std::sin(angle) * radius + center.y());
|
||||
}
|
||||
return toRet;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
|
||||
template <typename T> T catmull_rom_get_t(T t, T alpha, const Vector<T, 2>& p0, const Vector<T, 2>& p1) {
|
||||
Vector<T, 2> d = p1 - p0;
|
||||
T a = d.dot(d);
|
||||
T b = std::pow(a, alpha * 0.5);
|
||||
return (b + t);
|
||||
}
|
||||
template <typename T> Vector<T, 2> catmull_rom(const Vector<T, 2>& p0, const Vector<T, 2>& p1, const Vector<T, 2>& p2, const Vector<T, 2>& p3, T t, T alpha = 0.5) {
|
||||
T t0 = 0.0;
|
||||
T t1 = catmull_rom_get_t(t0, alpha, p0, p1);
|
||||
T t2 = catmull_rom_get_t(t1, alpha, p1, p2);
|
||||
T t3 = catmull_rom_get_t(t2, alpha, p2, p3);
|
||||
t = std::lerp(t1, t2, t);
|
||||
Vector<T, 2> A1 = ( t1-t )/( t1-t0 )*p0 + ( t-t0 )/( t1-t0 )*p1;
|
||||
Vector<T, 2> A2 = ( t2-t )/( t2-t1 )*p1 + ( t-t1 )/( t2-t1 )*p2;
|
||||
Vector<T, 2> A3 = ( t3-t )/( t3-t2 )*p2 + ( t-t2 )/( t3-t2 )*p3;
|
||||
Vector<T, 2> B1 = ( t2-t )/( t2-t0 )*A1 + ( t-t0 )/( t2-t0 )*A2;
|
||||
Vector<T, 2> B2 = ( t3-t )/( t3-t1 )*A2 + ( t-t1 )/( t3-t1 )*A3;
|
||||
Vector<T, 2> C = ( t2-t )/( t2-t1 )*B1 + ( t-t1 )/( t2-t1 )*B2;
|
||||
return C;
|
||||
}
|
||||
|
||||
template <typename T, int S> T transform_scalar(const Transform<T, 2, S>& transform, T scalar) {
|
||||
return (transform.linear() * Vector<T, 2>{scalar, scalar}).x();
|
||||
}
|
||||
|
||||
template <typename T> std::array<Vector<T, 2>, 4> triangle_from_rect_points(const Vector<T, 2>& min, const Vector<T, 2>& max) {
|
||||
return {min, {min.x(), max.y()}, max, {max.x(), min.y()}};
|
||||
}
|
||||
46
include/Helpers/Networking/ByteStream.cpp
Normal file
46
include/Helpers/Networking/ByteStream.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#include "ByteStream.hpp"
|
||||
#include <cereal/archives/portable_binary.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include "NetLibrary.hpp"
|
||||
|
||||
ByteMemBuf::ByteMemBuf(char* p, size_t l) {
|
||||
setg(p, p, p + l);
|
||||
|
||||
}
|
||||
|
||||
ByteMemStream::ByteMemStream(char* p, size_t l):
|
||||
std::istream(&buffer),
|
||||
buffer(p, l)
|
||||
{
|
||||
rdbuf(&buffer);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragment_message(std::string_view v, uint64_t stride) {
|
||||
if(v.length() <= stride)
|
||||
return {};
|
||||
uint64_t nextByte = 0;
|
||||
std::vector<std::shared_ptr<std::stringstream>> toRet;
|
||||
toRet.emplace_back(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*toRet.back());
|
||||
m((MessageCommandType)0, (uint64_t)v.length(), cereal::binary_data(v.data() + nextByte, stride));
|
||||
}
|
||||
nextByte += stride;
|
||||
for(;;) {
|
||||
if((v.length() - nextByte) <= stride) {
|
||||
toRet.emplace_back(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*toRet.back());
|
||||
m((MessageCommandType)0, cereal::binary_data(v.data() + nextByte, (uint64_t)(v.length() - nextByte)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
toRet.emplace_back(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*toRet.back());
|
||||
m((MessageCommandType)0, cereal::binary_data(v.data() + nextByte, stride));
|
||||
}
|
||||
nextByte += stride;
|
||||
}
|
||||
return toRet;
|
||||
}
|
||||
19
include/Helpers/Networking/ByteStream.hpp
Normal file
19
include/Helpers/Networking/ByteStream.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
// https://tuttlem.github.io/2014/08/18/getting-istream-to-work-off-a-byte-array.html
|
||||
class ByteMemBuf : public std::basic_streambuf<char> {
|
||||
public:
|
||||
ByteMemBuf(char* p, size_t l);
|
||||
};
|
||||
class ByteMemStream : public std::istream {
|
||||
public:
|
||||
ByteMemStream(char* p, size_t l);
|
||||
private:
|
||||
ByteMemBuf buffer;
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragment_message(std::string_view v, uint64_t stride);
|
||||
165
include/Helpers/Networking/NetClient.cpp
Normal file
165
include/Helpers/Networking/NetClient.cpp
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
#include "NetClient.hpp"
|
||||
#include "NetLibrary.hpp"
|
||||
#include "NetServer.hpp"
|
||||
#include <chrono>
|
||||
#include <Helpers/Logger.hpp>
|
||||
|
||||
NetClient::NetClient(const std::string& serverFullID) {
|
||||
receiveQueue = std::make_shared<ReceiveQueue>();
|
||||
this->serverFullID = serverFullID;
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
NetClient::NetClient(NetServer& server) {
|
||||
receiveQueue = std::make_shared<ReceiveQueue>();
|
||||
directConnectServer = &server;
|
||||
server.directConnectClient = this;
|
||||
server.directConnectClientData = std::make_shared<NetServer::ClientData>();
|
||||
server.directConnectClientData->lastMessageTime = std::chrono::steady_clock::now();
|
||||
server.clients.emplace_back(server.directConnectClientData);
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void NetClient::init_channel(const std::string& channelName, std::shared_ptr<rtc::DataChannel> channel) {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
auto commandChannelOnMessage = [channelName, rQueueWeak = make_weak_ptr(receiveQueue)](rtc::message_variant data) {
|
||||
auto rQueue = rQueueWeak.lock();
|
||||
if(rQueue) {
|
||||
std::scoped_lock rQueueLock(rQueue->mut);
|
||||
rQueue->messages.emplace(channelName, std::get<rtc::binary>(data));
|
||||
}
|
||||
};
|
||||
channel->onMessage(commandChannelOnMessage);
|
||||
channel->onClosed([cWeak = make_weak_ptr(shared_from_this())]() {
|
||||
auto cLock = cWeak.lock();
|
||||
if(cLock)
|
||||
cLock->isDisconnected = true;
|
||||
});
|
||||
channels[channelName] = channel;
|
||||
Logger::get().log("INFO", "Client channel setup: " + channelName);
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void NetClient::update() {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
if(std::chrono::steady_clock::now() - lastMessageTime >= NetLibrary::TIMEOUT_DURATION) {
|
||||
Logger::get().log("INFO", "Connection timed out");
|
||||
isDisconnected = true;
|
||||
return;
|
||||
}
|
||||
parse_received_messages();
|
||||
send_queued_messages();
|
||||
}
|
||||
|
||||
void NetClient::parse_received_messages() {
|
||||
std::scoped_lock rQueueLock(receiveQueue->mut);
|
||||
while(!receiveQueue->messages.empty()) {
|
||||
auto& receivedMessage = receiveQueue->messages.front();
|
||||
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
|
||||
size_t messageOrderDisplacement = 0;
|
||||
if(receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL || receivedMessage.channel == RELIABLE_COMMAND_CHANNEL) {
|
||||
MessageOrder messageOrder = NetLibrary::get_message_order(receivedMessage.data);
|
||||
if(nextMessageOrderToExpect > messageOrder && receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL) {
|
||||
receiveQueue->messages.pop();
|
||||
continue;
|
||||
}
|
||||
nextMessageOrderToExpect = std::max(messageOrder + 1, nextMessageOrderToExpect);
|
||||
messageOrderDisplacement = sizeof(MessageOrder);
|
||||
}
|
||||
|
||||
ByteMemStream strm((char*)(receivedMessage.data.data() + messageOrderDisplacement), receivedMessage.data.size() - messageOrderDisplacement);
|
||||
cereal::PortableBinaryInputArchive inArchive(strm);
|
||||
|
||||
auto& spfm = pfm[receivedMessage.channel];
|
||||
|
||||
MessageCommandType commandID;
|
||||
inArchive(commandID);
|
||||
|
||||
if(commandID == 0) {
|
||||
if(spfm.partialFragmentMessage.size() == 0) {
|
||||
spfm.partialFragmentMessageLoc = 0;
|
||||
uint64_t messageSize;
|
||||
inArchive(messageSize);
|
||||
spfm.partialFragmentMessage.resize(messageSize);
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
|
||||
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
|
||||
}
|
||||
else if(spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc <= NetLibrary::FRAGMENT_MESSAGE_STRIDE) {
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc));
|
||||
ByteMemStream completeStrm(spfm.partialFragmentMessage.data(), spfm.partialFragmentMessage.size());
|
||||
cereal::PortableBinaryInputArchive completeArchive(completeStrm);
|
||||
MessageCommandType completeCommandID;
|
||||
completeArchive(completeCommandID);
|
||||
recvCallbacks[completeCommandID](completeArchive);
|
||||
spfm.partialFragmentMessage.clear();
|
||||
spfm.partialFragmentMessageLoc = 0;
|
||||
}
|
||||
else {
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
|
||||
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
|
||||
}
|
||||
}
|
||||
else
|
||||
recvCallbacks[commandID](inArchive);
|
||||
|
||||
receiveQueue->messages.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void NetClient::send_queued_messages() {
|
||||
for(auto& [channelName, messageQueue] : messageQueues) {
|
||||
bool addMessageOrder = channelName == UNRELIABLE_COMMAND_CHANNEL || channelName == RELIABLE_COMMAND_CHANNEL;
|
||||
if(directConnectServer) {
|
||||
auto& c = directConnectServer->directConnectClientData;
|
||||
std::scoped_lock rMessLock(c->receivedMessagesMutex);
|
||||
while(!messageQueue.empty()) {
|
||||
std::string toInsert;
|
||||
if(addMessageOrder)
|
||||
toInsert = NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str());
|
||||
else
|
||||
toInsert = messageQueue.front()->str();
|
||||
rtc::binary b((std::byte*)toInsert.c_str(), (std::byte*)(toInsert.c_str() + toInsert.length()));
|
||||
c->receivedMessages.emplace(channelName, b);
|
||||
messageQueue.pop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto& channel = channels[channelName];
|
||||
if(channel) {
|
||||
if(channel->isOpen()) {
|
||||
while(!messageQueue.empty()) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL)
|
||||
break;
|
||||
#endif
|
||||
if(addMessageOrder)
|
||||
send_str_as_bytes(channel, NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str()));
|
||||
else
|
||||
send_str_as_bytes(channel, messageQueue.front()->str());
|
||||
messageQueue.pop();
|
||||
}
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL && channelName == UNRELIABLE_COMMAND_CHANNEL)
|
||||
messageQueue = std::queue<std::shared_ptr<std::stringstream>>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NetClient::is_disconnected() {
|
||||
return isDisconnected;
|
||||
}
|
||||
|
||||
void NetClient::send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str) {
|
||||
channel->send((const std::byte*)str.c_str(), str.size());
|
||||
}
|
||||
|
||||
void NetClient::add_recv_callback(MessageCommandType commandID, const NetClientRecvCallback& callback) {
|
||||
recvCallbacks[commandID] = callback;
|
||||
}
|
||||
83
include/Helpers/Networking/NetClient.hpp
Normal file
83
include/Helpers/Networking/NetClient.hpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <rtc/rtc.hpp>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/archives/portable_binary.hpp>
|
||||
#include <queue>
|
||||
#include "ByteStream.hpp"
|
||||
#include "NetLibrary.hpp"
|
||||
|
||||
typedef std::function<void(cereal::PortableBinaryInputArchive&)> NetClientRecvCallback;
|
||||
|
||||
class NetClient : public std::enable_shared_from_this<NetClient> {
|
||||
public:
|
||||
NetClient(const std::string& serverFullID);
|
||||
NetClient(NetServer& server);
|
||||
template <typename... Args> void send_items_to_server(const std::string& channel, Args&&... items) {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
auto ss(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*ss);
|
||||
(m(items), ...);
|
||||
}
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
|
||||
auto& messageQueue = messageQueues[channel];
|
||||
if(fragmentedMessage.empty())
|
||||
messageQueue.emplace(ss);
|
||||
else if(channel != UNRELIABLE_COMMAND_CHANNEL) { // Just drop unreliable messages that are fragmented
|
||||
for(auto& ss2 : fragmentedMessage)
|
||||
messageQueue.emplace(ss2);
|
||||
}
|
||||
}
|
||||
void update();
|
||||
void add_recv_callback(MessageCommandType commandID, const NetClientRecvCallback& callback);
|
||||
bool is_disconnected();
|
||||
|
||||
private:
|
||||
std::string localID;
|
||||
void init_channel(const std::string& channelName, std::shared_ptr<rtc::DataChannel> channel);
|
||||
void parse_received_messages();
|
||||
void send_queued_messages();
|
||||
void send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str);
|
||||
|
||||
std::unordered_map<std::string, std::queue<std::shared_ptr<std::stringstream>>> messageQueues;
|
||||
|
||||
struct ReceiveQueue {
|
||||
std::mutex mut;
|
||||
struct ReceivedMessage {
|
||||
std::string channel;
|
||||
rtc::binary data;
|
||||
};
|
||||
std::queue<ReceivedMessage> messages;
|
||||
};
|
||||
std::shared_ptr<ReceiveQueue> receiveQueue;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<rtc::DataChannel>> channels;
|
||||
|
||||
struct PartialFragmentMessage {
|
||||
std::string partialFragmentMessage;
|
||||
uint64_t partialFragmentMessageLoc = 0;
|
||||
};
|
||||
std::unordered_map<std::string, PartialFragmentMessage> pfm;
|
||||
|
||||
std::unordered_map<MessageCommandType, NetClientRecvCallback> recvCallbacks;
|
||||
|
||||
NetServer* directConnectServer = nullptr;
|
||||
std::atomic<bool> isDisconnected = false;
|
||||
|
||||
MessageOrder nextMessageOrderToSend = 0;
|
||||
MessageOrder nextMessageOrderToExpect = 0;
|
||||
|
||||
std::string serverFullID;
|
||||
bool fullyRegistered = false;
|
||||
|
||||
std::chrono::steady_clock::time_point lastMessageTime;
|
||||
|
||||
friend class NetLibrary;
|
||||
friend class NetServer;
|
||||
};
|
||||
389
include/Helpers/Networking/NetLibrary.cpp
Normal file
389
include/Helpers/Networking/NetLibrary.cpp
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
#include "NetLibrary.hpp"
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <rtc/peerconnection.hpp>
|
||||
#include "../Logger.hpp"
|
||||
#include <rtc/rtc.hpp>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <Helpers/Random.hpp>
|
||||
#include "NetClient.hpp"
|
||||
#include "NetServer.hpp"
|
||||
#include <bit>
|
||||
|
||||
std::atomic<bool> NetLibrary::alreadyInitialized = false;
|
||||
std::string NetLibrary::signalingAddr;
|
||||
std::shared_ptr<rtc::WebSocket> NetLibrary::ws;
|
||||
std::unordered_map<std::string, std::shared_ptr<NetLibrary::PeerData>> NetLibrary::peers;
|
||||
std::string NetLibrary::globalID;
|
||||
rtc::Configuration NetLibrary::config;
|
||||
std::mutex NetLibrary::serverListMutex;
|
||||
std::mutex NetLibrary::clientListMutex;
|
||||
std::mutex NetLibrary::peerListMutex;
|
||||
std::vector<std::weak_ptr<NetClient>> NetLibrary::clients;
|
||||
std::vector<std::weak_ptr<NetServer>> NetLibrary::servers;
|
||||
std::promise<void> NetLibrary::wsPromise;
|
||||
|
||||
void NetLibrary::init() {
|
||||
if(alreadyInitialized)
|
||||
return;
|
||||
|
||||
using json = nlohmann::json;
|
||||
std::ifstream f("data/config/p2p.json");
|
||||
|
||||
std::vector<std::string> stunServerList;
|
||||
std::vector<std::string> turnServerList;
|
||||
|
||||
rtc::InitLogger(rtc::LogLevel::Info, [](rtc::LogLevel level, std::string message) {
|
||||
Logger::get().log("INFO", "[LibDataChannel Log Level " + std::to_string(static_cast<int>(level)) + "] " + message);
|
||||
});
|
||||
|
||||
if(f.is_open()) {
|
||||
try {
|
||||
json j;
|
||||
f >> j;
|
||||
|
||||
j.at("signalingServer").get_to<std::string>(signalingAddr);
|
||||
|
||||
std::vector<std::string> stunList = j.at("stunList").get<std::vector<std::string>>();
|
||||
for(std::string& s : stunList) {
|
||||
if(s.substr(0, 5).compare("stun:"))
|
||||
s.insert(0, "stun:");
|
||||
config.iceServers.emplace_back(s);
|
||||
}
|
||||
}
|
||||
catch(...) {
|
||||
throw std::runtime_error("[NetLibrary::init] Invalid config/p2p.json");
|
||||
}
|
||||
}
|
||||
else
|
||||
throw std::runtime_error("[NetLibrary::init] Could not open config/p2p.json");
|
||||
|
||||
f.close();
|
||||
|
||||
|
||||
// Not sure why, but TLS verification fails on mac
|
||||
// Should be fixed later, but for now this is fine, as the signaling server doesn't have
|
||||
// critical information
|
||||
#ifdef __APPLE__
|
||||
rtc::WebSocket::Configuration wsConfig;
|
||||
wsConfig.disableTlsVerification = true;
|
||||
ws = std::make_shared<rtc::WebSocket>(wsConfig);
|
||||
#else
|
||||
ws = std::make_shared<rtc::WebSocket>();
|
||||
#endif
|
||||
|
||||
auto wsFuture = wsPromise.get_future();
|
||||
|
||||
ws->onOpen([]() {
|
||||
Logger::get().log("INFO", "Websocket connected, signaling ready");
|
||||
wsPromise.set_value();
|
||||
});
|
||||
|
||||
ws->onError([](std::string s) {
|
||||
Logger::get().log("INFO", "Websocket error: " + s);
|
||||
wsPromise.set_exception(std::make_exception_ptr(std::runtime_error(s)));
|
||||
});
|
||||
|
||||
ws->onClosed([]() {
|
||||
Logger::get().log("INFO", "Websocket closed");
|
||||
destroy();
|
||||
});
|
||||
|
||||
ws->onMessage([wws = make_weak_ptr(ws)](auto data) {
|
||||
if(!std::holds_alternative<std::string>(data))
|
||||
return;
|
||||
|
||||
json message = json::parse(std::get<std::string>(data));
|
||||
|
||||
auto itID = message.find("id");
|
||||
if(itID == message.end())
|
||||
return;
|
||||
auto id = itID->get<std::string>();
|
||||
|
||||
auto itType = message.find("type");
|
||||
auto type = itType->get<std::string>();
|
||||
|
||||
std::shared_ptr<rtc::PeerConnection> peerConnection;
|
||||
{
|
||||
std::scoped_lock peerListLock(peerListMutex);
|
||||
auto peerIt = peers.find(id);
|
||||
if(peerIt != peers.end())
|
||||
peerConnection = peerIt->second->connection;
|
||||
else if(type == "offer") {
|
||||
Logger::get().log("INFO", "Websocket answering to " + id);
|
||||
auto peer = setup_peer_connection(config, wws, id);
|
||||
peers[id] = peer;
|
||||
peerConnection = peer->connection;
|
||||
}
|
||||
}
|
||||
|
||||
if(!peerConnection)
|
||||
return;
|
||||
|
||||
if(type == "offer" || type == "answer") {
|
||||
auto sdp = message["description"].get<std::string>();
|
||||
peerConnection->setRemoteDescription(rtc::Description(sdp, type));
|
||||
}
|
||||
else if(type == "candidate") {
|
||||
auto sdp = message["candidate"].get<std::string>();
|
||||
auto mid = message["mid"].get<std::string>();
|
||||
peerConnection->addRemoteCandidate(rtc::Candidate(sdp, mid));
|
||||
}
|
||||
});
|
||||
|
||||
get_global_id();
|
||||
|
||||
Logger::get().log("INFO", "NetLibrary global id: " + globalID);
|
||||
|
||||
const std::string wsPrefix = signalingAddr.find("://") == std::string::npos ? "ws://" : "";
|
||||
const std::string url = wsPrefix + signalingAddr + "/" + globalID;
|
||||
|
||||
Logger::get().log("INFO", "Websocket URL is: " + url);
|
||||
ws->open(url);
|
||||
|
||||
Logger::get().log("INFO", "NetLibrary waiting for signaling to be connected...");
|
||||
//wsFuture.get();
|
||||
|
||||
alreadyInitialized = true;
|
||||
}
|
||||
|
||||
std::string NetLibrary::get_random_server_local_id() {
|
||||
return Random::get().alphanumeric_str(LOCALID_LEN);
|
||||
}
|
||||
|
||||
const std::string& NetLibrary::get_global_id() {
|
||||
if(globalID.empty())
|
||||
globalID = Random::get().alphanumeric_str(GLOBALID_LEN);
|
||||
return globalID;
|
||||
}
|
||||
|
||||
void NetLibrary::register_server(std::shared_ptr<NetServer> server) {
|
||||
std::scoped_lock serverListLock(serverListMutex);
|
||||
servers.emplace_back(server);
|
||||
}
|
||||
|
||||
void NetLibrary::register_client(std::shared_ptr<NetClient> client) {
|
||||
client->localID = Random::get().alphanumeric_str(LOCALID_LEN);
|
||||
std::scoped_lock serverListLock(clientListMutex);
|
||||
clients.emplace_back(client);
|
||||
}
|
||||
|
||||
void NetLibrary::finish_client_registration(std::shared_ptr<NetClient> client) {
|
||||
try {
|
||||
std::string serverGlobalID = client->serverFullID.substr(0, GLOBALID_LEN);
|
||||
std::string serverLocalID = client->serverFullID.substr(GLOBALID_LEN);
|
||||
std::scoped_lock peerListLock(peerListMutex);
|
||||
if(!peers.contains(serverGlobalID)) {
|
||||
auto peer = setup_peer_connection(config, ws, serverGlobalID);
|
||||
peers[serverGlobalID] = peer;
|
||||
peer->channel = peer->connection->createDataChannel(MAIN_NETLIBRARY_CHANNEL);
|
||||
setup_main_data_channel(peer, serverGlobalID);
|
||||
}
|
||||
peers[serverGlobalID]->messageQueue.emplace(client->localID + serverLocalID);
|
||||
client->fullyRegistered = true;
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
Logger::get().log("INFO", "Invalid client registration: " + std::string(e.what()));
|
||||
client->isDisconnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NetLibrary::update() {
|
||||
{
|
||||
std::scoped_lock clientListLock(clientListMutex);
|
||||
std::erase_if(clients, [](auto& cWeak) {
|
||||
auto cLock = cWeak.lock();
|
||||
if(!cLock)
|
||||
return true;
|
||||
if(!cLock->fullyRegistered && ws->isOpen())
|
||||
finish_client_registration(cLock);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
std::scoped_lock serverListLock(serverListMutex);
|
||||
std::erase_if(servers, [](auto& sWeak) {
|
||||
return !sWeak.lock();
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
std::scoped_lock peerListLock(peerListMutex);
|
||||
std::erase_if(peers, [](auto& a) {
|
||||
auto& p = a.second;
|
||||
rtc::PeerConnection::State s = p->connection->state();
|
||||
if(s == rtc::PeerConnection::State::Disconnected || s == rtc::PeerConnection::State::Closed || s == rtc::PeerConnection::State::Failed)
|
||||
return true;
|
||||
if(p->channel) {
|
||||
if(p->channel->isOpen()) {
|
||||
while(!p->messageQueue.empty()) {
|
||||
p->channel->send(p->messageQueue.front());
|
||||
Logger::get().log("INFO", "Sent main channel message: " + p->messageQueue.front());
|
||||
p->messageQueue.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<NetLibrary::PeerData> NetLibrary::setup_peer_connection(const rtc::Configuration& config, std::weak_ptr<rtc::WebSocket> wws, std::string id) {
|
||||
using json = nlohmann::json;
|
||||
auto peer = std::make_shared<PeerData>();
|
||||
peer->connection = std::make_shared<rtc::PeerConnection>(config);
|
||||
|
||||
peer->connection->onLocalDescription([wws, id](rtc::Description description) {
|
||||
json message = {{"id", id},
|
||||
{"type", description.typeString()},
|
||||
{"description", std::string(description)}};
|
||||
|
||||
if (auto ws = wws.lock())
|
||||
ws->send(message.dump());
|
||||
});
|
||||
|
||||
peer->connection->onLocalCandidate([wws, id](rtc::Candidate candidate) {
|
||||
json message = {{"id", id},
|
||||
{"type", "candidate"},
|
||||
{"candidate", std::string(candidate)},
|
||||
{"mid", candidate.mid()}};
|
||||
|
||||
if (auto ws = wws.lock())
|
||||
ws->send(message.dump());
|
||||
});
|
||||
|
||||
peer->connection->onDataChannel([id, peerWeak = make_weak_ptr(peer)](std::shared_ptr<rtc::DataChannel> dc) {
|
||||
auto peer = peerWeak.lock();
|
||||
if(peer) {
|
||||
if(!peer->channel) {
|
||||
if(dc->label() == MAIN_NETLIBRARY_CHANNEL) {
|
||||
peer->channel = dc;
|
||||
setup_main_data_channel(peer, id);
|
||||
}
|
||||
else {
|
||||
Logger::get().log("INFO", "Client connected with incorrect first channel name: " + dc->label() + ". Could be from a different version of the program");
|
||||
dc->send("REFUSE");
|
||||
peer->connection->close();
|
||||
}
|
||||
}
|
||||
else
|
||||
channel_created_in_client(dc);
|
||||
}
|
||||
});
|
||||
|
||||
return peer;
|
||||
}
|
||||
|
||||
void NetLibrary::setup_main_data_channel(std::shared_ptr<PeerData> peer, const std::string& id) {
|
||||
peer->channel->onOpen([id]() {
|
||||
Logger::get().log("INFO", "Main data channel has opened on " + id);
|
||||
});
|
||||
peer->channel->onClosed([id]() {
|
||||
Logger::get().log("INFO", "Main data channel has closed on " + id);
|
||||
});
|
||||
peer->channel->onMessage([id, pConWeak = make_weak_ptr(peer->connection)](auto data) {
|
||||
if(!std::holds_alternative<std::string>(data)) { // Main channel messages will only carry text
|
||||
Logger::get().log("INFO", "Main data channel received binary message, which is not expected");
|
||||
return;
|
||||
}
|
||||
std::string message = std::get<std::string>(data);
|
||||
if(message == "REFUSE") {
|
||||
Logger::get().log("USERINFO", "Refused connection. Could be on a different version");
|
||||
auto pConLock = pConWeak.lock();
|
||||
if(pConLock)
|
||||
pConLock->close();
|
||||
}
|
||||
else {
|
||||
std::string clientLocalID = message.substr(0, LOCALID_LEN);
|
||||
std::string serverLocalID = message.substr(LOCALID_LEN);
|
||||
Logger::get().log("INFO", "Main data channel received message " + message);
|
||||
auto pConLock = pConWeak.lock();
|
||||
if(pConLock)
|
||||
assign_client_connection_to_server(serverLocalID, clientLocalID, pConLock);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NetLibrary::assign_client_connection_to_server(const std::string& serverLocalID, const std::string& clientLocalID, std::shared_ptr<rtc::PeerConnection> connection) {
|
||||
std::scoped_lock serverListLock(serverListMutex);
|
||||
auto serverIt = std::find_if(servers.begin(), servers.end(), [&serverLocalID](auto& s) {
|
||||
auto sLock = s.lock();
|
||||
if(!sLock)
|
||||
return false;
|
||||
return sLock->localID == serverLocalID;
|
||||
});
|
||||
if(serverIt != servers.end()) {
|
||||
auto sLock = serverIt->lock();
|
||||
if(sLock)
|
||||
sLock->client_connected(connection, clientLocalID);
|
||||
}
|
||||
}
|
||||
|
||||
void NetLibrary::channel_created_in_client(std::shared_ptr<rtc::DataChannel> channel) {
|
||||
std::string clientID = channel->label().substr(0, LOCALID_LEN);
|
||||
std::string channelName = channel->label().substr(LOCALID_LEN);
|
||||
std::scoped_lock clientListLock(clientListMutex);
|
||||
auto clientIt = std::find_if(clients.begin(), clients.end(), [&clientID](auto& c) {
|
||||
auto cLock = c.lock();
|
||||
if(!cLock)
|
||||
return false;
|
||||
return cLock->localID == clientID;
|
||||
});
|
||||
if(clientIt != clients.end()) {
|
||||
auto cLock = clientIt->lock();
|
||||
if(cLock)
|
||||
cLock->init_channel(channelName, channel);
|
||||
}
|
||||
}
|
||||
|
||||
std::string NetLibrary::attach_message_order_and_update(MessageOrder& order, const std::string& message) {
|
||||
union {
|
||||
MessageOrder o;
|
||||
char b[sizeof(MessageOrder)];
|
||||
} u;
|
||||
u.o = (std::endian::native == std::endian::little) ? std::byteswap(order) : order;
|
||||
order++;
|
||||
return std::string(u.b, sizeof(MessageOrder)) + message;
|
||||
}
|
||||
|
||||
MessageOrder NetLibrary::get_message_order(rtc::binary& message) {
|
||||
union {
|
||||
MessageOrder o;
|
||||
char b[sizeof(MessageOrder)];
|
||||
} u;
|
||||
std::memcpy(u.b, message.data(), sizeof(MessageOrder));
|
||||
if(std::endian::native == std::endian::little)
|
||||
u.o = std::byteswap(u.o);
|
||||
return u.o;
|
||||
}
|
||||
|
||||
void NetLibrary::destroy() {
|
||||
ws = nullptr;
|
||||
{
|
||||
std::scoped_lock peerListLock(peerListMutex);
|
||||
peers.clear();
|
||||
}
|
||||
{
|
||||
std::scoped_lock clientListLock(clientListMutex);
|
||||
for(auto& c : clients) {
|
||||
auto cLock = c.lock();
|
||||
if(cLock)
|
||||
cLock->isDisconnected = true;
|
||||
}
|
||||
clients.clear();
|
||||
}
|
||||
{
|
||||
std::scoped_lock serverListLock(serverListMutex);
|
||||
for(auto& s : servers) {
|
||||
auto sLock = s.lock();
|
||||
if(sLock)
|
||||
sLock->isDisconnected = true;
|
||||
}
|
||||
servers.clear();
|
||||
}
|
||||
wsPromise = std::promise<void>();
|
||||
//globalID.clear(); // Do we need to do this? (Causes problems when global ID changes from the user's side after they copy a lobby address
|
||||
alreadyInitialized = false;
|
||||
}
|
||||
73
include/Helpers/Networking/NetLibrary.hpp
Normal file
73
include/Helpers/Networking/NetLibrary.hpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <rtc/rtc.hpp>
|
||||
#include <queue>
|
||||
|
||||
typedef uint64_t MessageOrder;
|
||||
class NetClient;
|
||||
class NetServer;
|
||||
|
||||
template <class T> std::weak_ptr<T> make_weak_ptr(std::shared_ptr<T> ptr) { return ptr; }
|
||||
|
||||
#define RELIABLE_COMMAND_CHANNEL "reliableCommand"
|
||||
#define UNRELIABLE_COMMAND_CHANNEL "unreliableCommand"
|
||||
#define RESOURCE_COMMAND_CHANNEL "resourceCommand"
|
||||
#define MAIN_NETLIBRARY_CHANNEL "main00001"
|
||||
|
||||
typedef uint8_t MessageCommandType;
|
||||
|
||||
class NetLibrary {
|
||||
public:
|
||||
static void init();
|
||||
static void update();
|
||||
static std::string get_random_server_local_id();
|
||||
static const std::string& get_global_id();
|
||||
|
||||
static void register_server(std::shared_ptr<NetServer> server);
|
||||
static void register_client(std::shared_ptr<NetClient> client);
|
||||
|
||||
static void destroy();
|
||||
|
||||
static constexpr size_t LOCALID_LEN = 5;
|
||||
static constexpr size_t GLOBALID_LEN = 20;
|
||||
private:
|
||||
static constexpr size_t FRAGMENT_MESSAGE_STRIDE = 4096;
|
||||
static constexpr size_t MAX_BUFFERED_DATA_PER_CHANNEL = 65500;
|
||||
static constexpr std::chrono::seconds TIMEOUT_DURATION = std::chrono::seconds(15);
|
||||
|
||||
static void finish_client_registration(std::shared_ptr<NetClient> client);
|
||||
|
||||
static void assign_client_connection_to_server(const std::string& serverLocalID, const std::string& clientLocalID, std::shared_ptr<rtc::PeerConnection> connection);
|
||||
static void channel_created_in_client(std::shared_ptr<rtc::DataChannel> channel);
|
||||
|
||||
static std::string attach_message_order_and_update(MessageOrder& order, const std::string& message);
|
||||
static MessageOrder get_message_order(rtc::binary& message);
|
||||
|
||||
struct PeerData {
|
||||
std::shared_ptr<rtc::PeerConnection> connection;
|
||||
std::shared_ptr<rtc::DataChannel> channel;
|
||||
std::queue<std::string> messageQueue;
|
||||
};
|
||||
|
||||
static void setup_main_data_channel(std::shared_ptr<PeerData> peer, const std::string& id);
|
||||
static std::shared_ptr<PeerData> setup_peer_connection(const rtc::Configuration& config, std::weak_ptr<rtc::WebSocket> wws, std::string id);
|
||||
|
||||
static std::atomic<bool> alreadyInitialized;
|
||||
static std::string signalingAddr;
|
||||
static std::shared_ptr<rtc::WebSocket> ws;
|
||||
static std::unordered_map<std::string, std::shared_ptr<PeerData>> peers;
|
||||
static std::string globalID;
|
||||
static rtc::Configuration config;
|
||||
|
||||
static std::mutex clientListMutex;
|
||||
static std::mutex serverListMutex;
|
||||
static std::mutex peerListMutex;
|
||||
static std::vector<std::weak_ptr<NetClient>> clients;
|
||||
static std::vector<std::weak_ptr<NetServer>> servers;
|
||||
|
||||
friend class NetClient;
|
||||
friend class NetServer;
|
||||
|
||||
static std::promise<void> wsPromise;
|
||||
};
|
||||
201
include/Helpers/Networking/NetServer.cpp
Normal file
201
include/Helpers/Networking/NetServer.cpp
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
#include "NetServer.hpp"
|
||||
#include "NetLibrary.hpp"
|
||||
#include <chrono>
|
||||
#include <rtc/peerconnection.hpp>
|
||||
#include <Helpers/Random.hpp>
|
||||
#include <Helpers/Logger.hpp>
|
||||
#include "NetClient.hpp"
|
||||
|
||||
NetServer::NetServer(const std::string& serverLocalID) {
|
||||
localID = serverLocalID;
|
||||
Logger::get().log("INFO", "[NetServer::NetServer] Server has local id: " + serverLocalID);
|
||||
}
|
||||
|
||||
void NetServer::update() {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
std::erase_if(clients, [&](auto& client) {
|
||||
bool timedOut = std::chrono::steady_clock::now() - client->lastMessageTime > NetLibrary::TIMEOUT_DURATION;
|
||||
if(client->setToDisconnect || timedOut) {
|
||||
if(disconnectCallback)
|
||||
disconnectCallback(*client);
|
||||
if(timedOut)
|
||||
Logger::get().log("INFO", "Connection timed out");
|
||||
return true;
|
||||
}
|
||||
client->parse_received_messages(*this);
|
||||
client->send_queued_messages(*this);
|
||||
return false;
|
||||
});
|
||||
|
||||
std::scoped_lock clientAddLock(clientsToAddMutex);
|
||||
clients.insert(clients.begin(), clientsToAdd.begin(), clientsToAdd.end());
|
||||
clientsToAdd.clear();
|
||||
}
|
||||
|
||||
bool NetServer::is_disconnected() {
|
||||
return isDisconnected;
|
||||
}
|
||||
|
||||
void NetServer::client_connected(std::shared_ptr<rtc::PeerConnection> connection, const std::string& clientLocalID) {
|
||||
if(isDisconnected)
|
||||
return;
|
||||
auto newClient = std::make_shared<ClientData>();
|
||||
newClient->lastMessageTime = std::chrono::steady_clock::now();
|
||||
|
||||
{
|
||||
std::scoped_lock clientAddLock(clientsToAddMutex);
|
||||
clientsToAdd.emplace_back(newClient);
|
||||
}
|
||||
|
||||
Logger::get().log("INFO", "Client connected and creating channels for: " + clientLocalID);
|
||||
newClient->channels.emplace(RELIABLE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + RELIABLE_COMMAND_CHANNEL));
|
||||
newClient->channels.emplace(RESOURCE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + RESOURCE_COMMAND_CHANNEL));
|
||||
rtc::DataChannelInit unreliableCommandInit;
|
||||
unreliableCommandInit.reliability.maxPacketLifeTime = std::chrono::milliseconds(2000);
|
||||
unreliableCommandInit.reliability.unordered = true;
|
||||
newClient->channels.emplace(UNRELIABLE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + UNRELIABLE_COMMAND_CHANNEL, unreliableCommandInit));
|
||||
|
||||
newClient->channels[RELIABLE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
|
||||
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
||||
if(clientLock) {
|
||||
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
|
||||
clientLock->receivedMessages.emplace(RELIABLE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
|
||||
}
|
||||
});
|
||||
// If any of the local channels are closed, we can assume the client has disconnected
|
||||
newClient->channels[RELIABLE_COMMAND_CHANNEL]->onClosed([weakClient = make_weak_ptr(newClient)]() {
|
||||
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
||||
if(clientLock)
|
||||
clientLock->setToDisconnect = true;
|
||||
});
|
||||
newClient->channels[UNRELIABLE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
|
||||
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
||||
if(clientLock) {
|
||||
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
|
||||
clientLock->receivedMessages.emplace(UNRELIABLE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
|
||||
}
|
||||
});
|
||||
newClient->channels[RESOURCE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
|
||||
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
||||
if(clientLock) {
|
||||
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
|
||||
clientLock->receivedMessages.emplace(RESOURCE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NetServer::ClientData::send_queued_messages(NetServer& server) {
|
||||
for(auto& [channelName, messageQueue] : messageQueues) {
|
||||
bool addMessageOrder = channelName == UNRELIABLE_COMMAND_CHANNEL || channelName == RELIABLE_COMMAND_CHANNEL;
|
||||
if(this == server.directConnectClientData.get()) {
|
||||
NetClient* c = server.directConnectClient;
|
||||
std::scoped_lock recvQueueLock(c->receiveQueue->mut);
|
||||
while(!messageQueue.empty()) {
|
||||
std::string toInsert;
|
||||
if(addMessageOrder)
|
||||
toInsert = NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str());
|
||||
else
|
||||
toInsert = messageQueue.front()->str();
|
||||
rtc::binary b((std::byte*)toInsert.c_str(), (std::byte*)(toInsert.c_str() + toInsert.length()));
|
||||
c->receiveQueue->messages.emplace(channelName, b);
|
||||
messageQueue.pop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto& channel = channels[channelName];
|
||||
if(channel) {
|
||||
if(channel->isOpen()) {
|
||||
while(!messageQueue.empty()) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL)
|
||||
break;
|
||||
#endif
|
||||
if(addMessageOrder)
|
||||
send_str_as_bytes(channel, NetLibrary::attach_message_order_and_update(nextMessageOrderToSend, messageQueue.front()->str()));
|
||||
else
|
||||
send_str_as_bytes(channel, messageQueue.front()->str());
|
||||
messageQueue.pop();
|
||||
}
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL && channelName == UNRELIABLE_COMMAND_CHANNEL)
|
||||
messageQueue = std::queue<std::shared_ptr<std::stringstream>>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetServer::ClientData::send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str) {
|
||||
channel->send((const std::byte*)str.c_str(), str.size());
|
||||
}
|
||||
|
||||
void NetServer::ClientData::parse_received_messages(NetServer& server) {
|
||||
std::scoped_lock recvMessLock(receivedMessagesMutex);
|
||||
while(!receivedMessages.empty()) {
|
||||
auto& receivedMessage = receivedMessages.front();
|
||||
lastMessageTime = std::chrono::steady_clock::now();
|
||||
|
||||
size_t messageOrderDisplacement = 0;
|
||||
if(receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL || receivedMessage.channel == RELIABLE_COMMAND_CHANNEL) {
|
||||
MessageOrder messageOrder = NetLibrary::get_message_order(receivedMessage.data);
|
||||
if(nextMessageOrderToExpect > messageOrder && receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL) {
|
||||
receivedMessages.pop();
|
||||
continue;
|
||||
}
|
||||
nextMessageOrderToExpect = std::max(messageOrder + 1, nextMessageOrderToExpect);
|
||||
messageOrderDisplacement = sizeof(MessageOrder);
|
||||
}
|
||||
|
||||
ByteMemStream strm((char*)(receivedMessage.data.data() + messageOrderDisplacement), receivedMessage.data.size() - messageOrderDisplacement);
|
||||
cereal::PortableBinaryInputArchive inArchive(strm);
|
||||
|
||||
auto& spfm = pfm[receivedMessage.channel];
|
||||
|
||||
MessageCommandType commandID;
|
||||
inArchive(commandID);
|
||||
|
||||
if(commandID == 0) {
|
||||
if(spfm.partialFragmentMessage.size() == 0) {
|
||||
spfm.partialFragmentMessageLoc = 0;
|
||||
uint64_t messageSize;
|
||||
inArchive(messageSize);
|
||||
spfm.partialFragmentMessage.resize(messageSize);
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
|
||||
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
|
||||
}
|
||||
else if(spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc <= NetLibrary::FRAGMENT_MESSAGE_STRIDE) {
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, spfm.partialFragmentMessage.size() - spfm.partialFragmentMessageLoc));
|
||||
ByteMemStream completeStrm(spfm.partialFragmentMessage.data(), spfm.partialFragmentMessage.size());
|
||||
cereal::PortableBinaryInputArchive completeArchive(completeStrm);
|
||||
MessageCommandType completeCommandID;
|
||||
completeArchive(completeCommandID);
|
||||
server.recvCallbacks[completeCommandID](*this, completeArchive);
|
||||
spfm.partialFragmentMessage.clear();
|
||||
spfm.partialFragmentMessageLoc = 0;
|
||||
}
|
||||
else {
|
||||
inArchive(cereal::binary_data(spfm.partialFragmentMessage.data() + spfm.partialFragmentMessageLoc, NetLibrary::FRAGMENT_MESSAGE_STRIDE));
|
||||
spfm.partialFragmentMessageLoc += NetLibrary::FRAGMENT_MESSAGE_STRIDE;
|
||||
}
|
||||
}
|
||||
else
|
||||
server.recvCallbacks[commandID](*this, inArchive);
|
||||
|
||||
receivedMessages.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void NetServer::add_recv_callback(MessageCommandType commandID, const NetServerRecvCallback& callback) {
|
||||
recvCallbacks[commandID] = callback;
|
||||
}
|
||||
|
||||
void NetServer::add_disconnect_callback(const NetServerDisconnectCallback& callback) {
|
||||
disconnectCallback = callback;
|
||||
}
|
||||
|
||||
NetServer::~NetServer() {
|
||||
if(directConnectClient)
|
||||
directConnectClient->isDisconnected = true;
|
||||
}
|
||||
134
include/Helpers/Networking/NetServer.hpp
Normal file
134
include/Helpers/Networking/NetServer.hpp
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <rtc/rtc.hpp>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <cereal/archives/portable_binary.hpp>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include "ByteStream.hpp"
|
||||
#include "NetLibrary.hpp"
|
||||
|
||||
class NetServer : public std::enable_shared_from_this<NetServer> {
|
||||
public:
|
||||
struct ClientData;
|
||||
typedef std::function<void(ClientData&, cereal::PortableBinaryInputArchive&)> NetServerRecvCallback;
|
||||
typedef std::function<void(ClientData&)> NetServerConnectCallback;
|
||||
typedef std::function<void(ClientData&)> NetServerDisconnectCallback;
|
||||
struct ClientData {
|
||||
std::unordered_map<std::string, std::queue<std::shared_ptr<std::stringstream>>> messages;
|
||||
struct PartialFragmentMessage {
|
||||
std::string partialFragmentMessage;
|
||||
uint64_t partialFragmentMessageLoc = 0;
|
||||
};
|
||||
std::unordered_map<std::string, PartialFragmentMessage> pfm;
|
||||
|
||||
MessageOrder nextMessageOrderToSend = 0;
|
||||
MessageOrder nextMessageOrderToExpect = 0;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<rtc::DataChannel>> channels;
|
||||
|
||||
std::mutex receivedMessagesMutex;
|
||||
struct ReceivedMessage {
|
||||
std::string channel;
|
||||
rtc::binary data;
|
||||
};
|
||||
std::queue<ReceivedMessage> receivedMessages;
|
||||
|
||||
std::unordered_map<std::string, std::queue<std::shared_ptr<std::stringstream>>> messageQueues;
|
||||
|
||||
uint64_t customID;
|
||||
|
||||
void send_queued_messages(NetServer& server);
|
||||
void parse_received_messages(NetServer& server);
|
||||
void send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str);
|
||||
|
||||
std::atomic<bool> setToDisconnect = false;
|
||||
std::chrono::steady_clock::time_point lastMessageTime;
|
||||
|
||||
friend class NetServer;
|
||||
friend class NetClient;
|
||||
};
|
||||
|
||||
NetServer(const std::string& serverLocalID);
|
||||
~NetServer();
|
||||
void update();
|
||||
template <typename... Args> void send_items_to_client(ClientData& client, const std::string& channel, Args&&... items) {
|
||||
auto ss(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*ss);
|
||||
(m(items), ...);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
|
||||
auto& messageQueue = client.messageQueues[channel];
|
||||
if(fragmentedMessage.empty())
|
||||
messageQueue.emplace(ss);
|
||||
else if(channel != UNRELIABLE_COMMAND_CHANNEL) { // Just drop unreliable messages that are fragmented
|
||||
for(auto& ss2 : fragmentedMessage)
|
||||
messageQueue.emplace(ss2);
|
||||
}
|
||||
}
|
||||
template <typename... Args> void send_items_to_all_clients(const std::string& channel, Args&&... items) {
|
||||
send_items_to_client_if([&](const ClientData& c) {
|
||||
return true;
|
||||
}, channel, items...);
|
||||
}
|
||||
template <typename... Args> void send_items_to_all_clients_except(ClientData& client, const std::string& channel, Args&&... items) {
|
||||
send_items_to_client_if([&](const ClientData& c) {
|
||||
return (&c) != (&client);
|
||||
}, channel, items...);
|
||||
}
|
||||
template <typename... Args> void send_items_to_client_if(std::function<bool(const ClientData&)> clientChecker, const std::string& channel, Args&&... items) {
|
||||
auto ss(std::make_shared<std::stringstream>());
|
||||
{
|
||||
cereal::PortableBinaryOutputArchive m(*ss);
|
||||
(m(items), ...);
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
|
||||
|
||||
for(auto& client : clients) {
|
||||
if(clientChecker(*client)) {
|
||||
auto& messageQueue = client->messageQueues[channel];
|
||||
if(fragmentedMessage.empty())
|
||||
messageQueue.emplace(ss);
|
||||
else if(channel != UNRELIABLE_COMMAND_CHANNEL) { // Just drop unreliable messages that are fragmented
|
||||
for(auto& ss2 : fragmentedMessage)
|
||||
messageQueue.emplace(ss2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_disconnected();
|
||||
|
||||
void add_recv_callback(MessageCommandType commandID, const NetServerRecvCallback& callback);
|
||||
void add_disconnect_callback(const NetServerDisconnectCallback& callback);
|
||||
private:
|
||||
void parse_received_messages();
|
||||
void send_queued_messages();
|
||||
void client_connected(std::shared_ptr<rtc::PeerConnection> connection, const std::string& clientLocalID);
|
||||
|
||||
std::atomic<bool> isDisconnected = false;
|
||||
|
||||
std::string localID;
|
||||
|
||||
std::mutex clientList;
|
||||
|
||||
std::vector<std::shared_ptr<ClientData>> clients;
|
||||
|
||||
// Adding an add list, because i dont want to block the entire clients vector with a mutex
|
||||
std::mutex clientsToAddMutex;
|
||||
std::vector<std::shared_ptr<ClientData>> clientsToAdd;
|
||||
|
||||
std::unordered_map<MessageCommandType, NetServerRecvCallback> recvCallbacks;
|
||||
NetServerDisconnectCallback disconnectCallback;
|
||||
|
||||
NetClient* directConnectClient = nullptr;
|
||||
std::shared_ptr<ClientData> directConnectClientData;
|
||||
|
||||
friend class NetLibrary;
|
||||
friend class NetClient;
|
||||
};
|
||||
26
include/Helpers/Random.cpp
Normal file
26
include/Helpers/Random.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#include "Random.hpp"
|
||||
|
||||
Random Random::global;
|
||||
|
||||
Random& Random::get() {
|
||||
return global;
|
||||
}
|
||||
|
||||
std::string Random::alphanumeric_str(size_t len) {
|
||||
std::string toRet;
|
||||
char allowedChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
size_t allowedCharCount = sizeof(allowedChars) / sizeof(char);
|
||||
|
||||
// We use allowedCharCount - 2 as the right bound because the range is inclusive,
|
||||
// and we dont want to include the implicit null terminator in the end of allowedChars
|
||||
// as a possible character
|
||||
for(size_t i = 0; i < len; i++)
|
||||
toRet.push_back(allowedChars[Random::get().int_range<size_t>(0, allowedCharCount - 2)]);
|
||||
|
||||
return toRet;
|
||||
}
|
||||
|
||||
Random::Random():
|
||||
randGen(std::random_device()())
|
||||
{
|
||||
}
|
||||
20
include/Helpers/Random.hpp
Normal file
20
include/Helpers/Random.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include <random>
|
||||
|
||||
class Random {
|
||||
public:
|
||||
static Random& get();
|
||||
Random();
|
||||
template <typename T> T int_range(T start, T end) {
|
||||
std::uniform_int_distribution<T> distribute(start, end);
|
||||
return distribute(randGen);
|
||||
}
|
||||
template <typename T> T real_range(T start, T end) {
|
||||
std::uniform_real_distribution<T> distribute(start, end);
|
||||
return distribute(randGen);
|
||||
}
|
||||
std::string alphanumeric_str(size_t len);
|
||||
std::mt19937_64 randGen;
|
||||
private:
|
||||
static Random global;
|
||||
};
|
||||
525
include/Helpers/SCollision.hpp
Normal file
525
include/Helpers/SCollision.hpp
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
#pragma once
|
||||
#include "Helpers/MathExtras.hpp"
|
||||
#include <Eigen/Dense>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
namespace SCollision {
|
||||
template <typename T> class AABB {
|
||||
public:
|
||||
typedef T UnderlyingType;
|
||||
|
||||
AABB() = default; // NOTE: Should not use the min and max numeric limits to initialize, since that wont work for WorldScalar
|
||||
AABB(const Vector<T, 2>& initMin, const Vector<T, 2>& initMax):
|
||||
min(initMin),
|
||||
max(initMax)
|
||||
{}
|
||||
Vector<T, 2> min;
|
||||
Vector<T, 2> max;
|
||||
Vector<T, 2> center() const { return (min + max) / T(2); }
|
||||
Vector<T, 2> bottom_left() const { return Vector<T, 2>{min.x(), max.y()}; }
|
||||
Vector<T, 2> top_right() const { return Vector<T, 2>{max.x(), min.y()}; }
|
||||
Vector<T, 2> dim() const { return max - min; }
|
||||
T width() const { return max.x() - min.x(); }
|
||||
T height() const { return max.y() - min.y(); }
|
||||
void include_aabb_in_bounds(const AABB<T>& other) {
|
||||
min = cwise_vec_min(other.min, min);
|
||||
max = cwise_vec_max(other.max, max);
|
||||
}
|
||||
void include_point_in_bounds(const Vector<T, 2>& p) {
|
||||
min = cwise_vec_min(p, min);
|
||||
max = cwise_vec_max(p, max);
|
||||
}
|
||||
bool operator==(const AABB<T>& other) const {
|
||||
return min == other.min && max == other.max;
|
||||
}
|
||||
template <typename S> AABB<S> transform(const std::function<Vector<S, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<S(const T&)>& transformLinearFunc) const {
|
||||
Vector<S, 2> aabb1 = transformFunc(min);
|
||||
Vector<S, 2> aabb2 = transformFunc(max);
|
||||
Vector<S, 2> aabb3 = transformFunc(top_right());
|
||||
Vector<S, 2> aabb4 = transformFunc(bottom_left());
|
||||
|
||||
AABB<S> toRet{aabb1, aabb1};
|
||||
|
||||
toRet.include_point_in_bounds(aabb1);
|
||||
toRet.include_point_in_bounds(aabb2);
|
||||
toRet.include_point_in_bounds(aabb3);
|
||||
toRet.include_point_in_bounds(aabb4);
|
||||
|
||||
return toRet;
|
||||
}
|
||||
template <typename S> AABB<S> cast() {
|
||||
AABB<S> toRet{min.template cast<S>(), max.template cast<S>()};
|
||||
return toRet;
|
||||
}
|
||||
};
|
||||
template <typename T> class Circle {
|
||||
public:
|
||||
typedef T UnderlyingType;
|
||||
|
||||
Circle() = default;
|
||||
Circle(const Vector<T, 2>& initPos, T initRadius):
|
||||
pos(initPos),
|
||||
radius(initRadius),
|
||||
bounds(pos - Vector<T, 2>{radius, radius}, pos + Vector<T, 2>{radius, radius})
|
||||
{}
|
||||
Vector<T, 2> pos;
|
||||
T radius;
|
||||
AABB<T> bounds;
|
||||
template <typename S> Circle<S> transform(const std::function<Vector<S, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<S(const T&)>& transformLinearFunc) const {
|
||||
Circle<S> newCirc = Circle<S>();
|
||||
newCirc.pos = transformFunc(pos);
|
||||
newCirc.radius = transformLinearFunc(radius);
|
||||
newCirc.bounds = AABB<S>(newCirc.pos - Vector<S, 2>{newCirc.radius, newCirc.radius}, newCirc.pos + Vector<S, 2>{newCirc.radius, newCirc.radius});
|
||||
return newCirc;
|
||||
}
|
||||
template <typename S> Circle<S> cast() {
|
||||
Circle<S> toRet;
|
||||
toRet.pos = pos.template cast<S>();
|
||||
toRet.radius = static_cast<S>(radius);
|
||||
toRet.bounds = bounds.template cast<S>();
|
||||
return toRet;
|
||||
}
|
||||
};
|
||||
template <typename T> class Triangle {
|
||||
public:
|
||||
typedef T UnderlyingType;
|
||||
|
||||
Triangle() = default;
|
||||
Triangle(const std::array<Vector<T, 2>, 3>& initP):
|
||||
p(initP),
|
||||
bounds(cwise_vec_min(cwise_vec_min(p[0], p[1]), p[2]), cwise_vec_max(cwise_vec_max(p[0], p[1]), p[2]))
|
||||
{}
|
||||
Triangle(const Vector<T, 2>& p1, const Vector<T, 2>& p2, const Vector<T, 2>& p3):
|
||||
p({p1, p2, p3}),
|
||||
bounds(cwise_vec_min(cwise_vec_min(p[0], p[1]), p[2]), cwise_vec_max(cwise_vec_max(p[0], p[1]), p[2]))
|
||||
{}
|
||||
template <typename S> Triangle<S> transform(const std::function<Vector<S, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<S(const T&)>& transformLinearFunc) const {
|
||||
Triangle<S> toRet = Triangle<S>();
|
||||
toRet.p[0] = transformFunc(p[0]);
|
||||
toRet.p[1] = transformFunc(p[1]);
|
||||
toRet.p[2] = transformFunc(p[2]);
|
||||
toRet.bounds = AABB<S>(cwise_vec_min(cwise_vec_min(toRet.p[0], toRet.p[1]), toRet.p[2]), cwise_vec_max(cwise_vec_max(toRet.p[0], toRet.p[1]), toRet.p[2]));
|
||||
return toRet;
|
||||
}
|
||||
template <typename S> Triangle<S> cast() {
|
||||
Triangle<S> toRet;
|
||||
toRet.p[0] = p[0].template cast<S>();
|
||||
toRet.p[1] = p[1].template cast<S>();
|
||||
toRet.p[2] = p[2].template cast<S>();
|
||||
toRet.bounds = bounds.template cast<S>();
|
||||
return toRet;
|
||||
}
|
||||
std::array<Vector<T, 2>, 3> p;
|
||||
AABB<T> bounds;
|
||||
};
|
||||
|
||||
template <typename T> struct ColliderCollection {
|
||||
typedef T UnderlyingType;
|
||||
|
||||
AABB<T> bounds;
|
||||
std::vector<AABB<T>> aabb;
|
||||
std::vector<Circle<T>> circle;
|
||||
std::vector<Triangle<T>> triangle;
|
||||
|
||||
void clear() {
|
||||
aabb.clear();
|
||||
circle.clear();
|
||||
triangle.clear();
|
||||
bounds = AABB<T>({0, 0}, {0, 0});
|
||||
}
|
||||
size_t size() const { return aabb.size() + circle.size() + triangle.size(); }
|
||||
bool empty() const { return aabb.empty() && circle.empty() && triangle.empty(); }
|
||||
template <typename ColliderList> void bound_limit(const ColliderList& list) {
|
||||
for(auto& o : list)
|
||||
bounds.include_aabb_in_bounds(get_bounds(o));
|
||||
}
|
||||
void recalculate_bounds() {
|
||||
bounds = AABB<T>();
|
||||
if(!aabb.empty())
|
||||
bounds = get_bounds(aabb.front());
|
||||
else if(!circle.empty())
|
||||
bounds = get_bounds(circle.front());
|
||||
else if(!triangle.empty())
|
||||
bounds = get_bounds(triangle.front());
|
||||
bound_limit(aabb);
|
||||
bound_limit(circle);
|
||||
bound_limit(triangle);
|
||||
}
|
||||
template <typename S> ColliderCollection<S> transform(const std::function<Vector<S, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<S(const T&)>& transformLinearFunc) const {
|
||||
ColliderCollection<S> toRet;
|
||||
for(auto& o : aabb)
|
||||
toRet.aabb.emplace_back(o.transform(transformFunc, transformLinearFunc));
|
||||
for(auto& o : circle)
|
||||
toRet.circle.emplace_back(o.transform(transformFunc, transformLinearFunc));
|
||||
for(auto& o : triangle)
|
||||
toRet.triangle.emplace_back(o.transform(transformFunc, transformLinearFunc));
|
||||
toRet.recalculate_bounds();
|
||||
return toRet;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> const AABB<T>& get_bounds(const Circle<T>& c) { return c.bounds; }
|
||||
template <typename T> const AABB<T>& get_bounds(const Triangle<T>& c) { return c.bounds; }
|
||||
template <typename T> const AABB<T>& get_bounds(const AABB<T>& a) { return a; }
|
||||
template <typename T> const AABB<T>& get_bounds(const ColliderCollection<T>& c) { return c.bounds; }
|
||||
|
||||
template<typename T> void generate_wide_line(ColliderCollection<T>& collection, const Vector<T, 2>& p1, const Vector<T, 2>& p2, T width, bool circleCaps) {
|
||||
if(p1 != p2) {
|
||||
Vector<T, 2> dir = p1 - p2;
|
||||
Vector<T, 2> perpVec = perpendicular_vec2(dir);
|
||||
perpVec.normalize();
|
||||
Vector<T, 2> perpWidth = (perpVec * width * T(0.5)).eval();
|
||||
collection.triangle.emplace_back(Triangle<T>{p1 + perpWidth, p1 - perpWidth, p2 + perpWidth});
|
||||
collection.triangle.emplace_back(Triangle<T>{p2 + perpWidth, p2 - perpWidth, p1 - perpWidth});
|
||||
}
|
||||
if(circleCaps) {
|
||||
collection.circle.emplace_back(Circle<T>{p1, width * T(0.5)});
|
||||
if(p1 != p2)
|
||||
collection.circle.emplace_back(Circle<T>{p2, width * T(0.5)});
|
||||
}
|
||||
collection.recalculate_bounds();
|
||||
}
|
||||
|
||||
template <typename T> Vector<T, 2> two_dim_vec_slerp(const Vector<T, 2>& v1, const Vector<T, 2>& v2) {
|
||||
Vector<T, 2> v1Norm = v1.normalized();
|
||||
Vector<T, 2> v2Norm = v2.normalized();
|
||||
Vector<T, 2> avg = (v1Norm + v2Norm) * 0.5;
|
||||
if(std::abs(avg.x()) < 0.00001 && std::abs(avg.y()) < 0.00001)
|
||||
return perpendicular_vec2(v1Norm).normalized();
|
||||
else
|
||||
return avg.normalized();
|
||||
}
|
||||
|
||||
template <typename T> void generate_polyline(ColliderCollection<T>& collection, const std::vector<Vector<T, 2>>& points, T width, bool closePath) {
|
||||
if(points.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<T, 2> perpPrev = perpendicular_vec2((points[1] - points.front()).eval());
|
||||
perpPrev.normalize();
|
||||
|
||||
std::array<Vector<T, 2>, 3> vertArray;
|
||||
unsigned newIndex = 0;
|
||||
|
||||
if(!closePath || points.size() == 2) {
|
||||
vertArray[0] = points.front() + perpPrev * width * 0.5;
|
||||
vertArray[1] = points.front() - perpPrev * width * 0.5;
|
||||
newIndex = 2;
|
||||
}
|
||||
|
||||
if(points.size() == 2) {
|
||||
vertArray[newIndex] = points.back() + perpPrev * width * 0.5;
|
||||
collection.triangle.emplace_back(Triangle<T>{vertArray});
|
||||
newIndex = (newIndex + 1) % 3;
|
||||
vertArray[newIndex] = points.back() - perpPrev * width * 0.5;
|
||||
collection.triangle.emplace_back(Triangle<T>{vertArray});
|
||||
return;
|
||||
}
|
||||
|
||||
for(size_t x = 0; x <= points.size(); x++) {
|
||||
size_t i = x;
|
||||
size_t iP1 = x + 1;
|
||||
size_t iM1 = x - 1;
|
||||
if(x == 0) {
|
||||
if(!closePath)
|
||||
continue;
|
||||
else
|
||||
iM1 = points.size() - 1;
|
||||
}
|
||||
else if(x == points.size() - 1) {
|
||||
if(!closePath)
|
||||
continue;
|
||||
else {
|
||||
iP1 = 0;
|
||||
}
|
||||
}
|
||||
else if(x == points.size()) {
|
||||
if(!closePath)
|
||||
continue;
|
||||
else {
|
||||
i = 0;
|
||||
iM1 = points.size() - 1;
|
||||
iP1 = 1;
|
||||
}
|
||||
}
|
||||
Vector<T, 2> v1Norm = (points[iM1] - points[i]).normalized();
|
||||
Vector<T, 2> v2Norm = (points[iP1] - points[i]).normalized();
|
||||
Vector<T, 2> perp = two_dim_vec_slerp(v1Norm, v2Norm);
|
||||
if(perp.dot(perpPrev) <= 0.0)
|
||||
perp = -perp;
|
||||
|
||||
T angle1 = std::acos((perp.dot(v1Norm) < 0 ? -perp : perp).dot(v1Norm));
|
||||
T angle2 = std::acos((perp.dot(v2Norm) < 0 ? -perp : perp).dot(v2Norm));
|
||||
T sinAngle1 = std::sin(angle1 + angle2);
|
||||
T alteredWidth = std::lerp(0.5, 0.707, sinAngle1);
|
||||
|
||||
vertArray[newIndex] = points[i] + perp * alteredWidth * width;
|
||||
newIndex = (newIndex + 1) % 3;
|
||||
if(x != 0)
|
||||
collection.triangle.emplace_back(Triangle<T>{vertArray});
|
||||
vertArray[newIndex] = points[i] - perp * alteredWidth * width;
|
||||
newIndex = (newIndex + 1) % 3;
|
||||
if(x != 0)
|
||||
collection.triangle.emplace_back(Triangle<T>{vertArray});
|
||||
|
||||
perpPrev = perp;
|
||||
}
|
||||
|
||||
if(!closePath) {
|
||||
Vector<T, 2> perp = perpendicular_vec2((points[points.size() - 2] - points.back()).eval());
|
||||
perp.normalize();
|
||||
if(perp.dot(perpPrev) <= 0.0)
|
||||
perp = -perp;
|
||||
vertArray[newIndex] = points.back() + perp * width * 0.5;
|
||||
newIndex = (newIndex + 1) % 3;
|
||||
collection.triangle.emplace_back(Triangle<T>{vertArray});
|
||||
vertArray[newIndex] = points.back() - perp * width * 0.5;
|
||||
newIndex = (newIndex + 1) % 3;
|
||||
collection.triangle.emplace_back(Triangle<T>{vertArray});
|
||||
}
|
||||
}
|
||||
|
||||
// https://blackpawn.com/texts/pointinpoly/default.html
|
||||
template <typename T> bool collide(const Vector<T, 2>& p, const Triangle<T>& t) {
|
||||
Vector<T, 2> v0 = t.p[2] - t.p[0];
|
||||
Vector<T, 2> v1 = t.p[1] - t.p[0];
|
||||
Vector<T, 2> v2 = p - t.p[0];
|
||||
|
||||
T dot00 = v0.dot(v0);
|
||||
T dot01 = v0.dot(v1);
|
||||
T dot02 = v0.dot(v2);
|
||||
T dot11 = v1.dot(v1);
|
||||
T dot12 = v1.dot(v2);
|
||||
|
||||
T invDenom = T(1) / (dot00 * dot11 - dot01 * dot01);
|
||||
T u = (dot11 * dot02 - dot01 * dot12) * invDenom;
|
||||
T v = (dot00 * dot12 - dot01 * dot02) * invDenom;
|
||||
|
||||
return (u >= T(0)) && (v >= T(0)) && (u + v < T(1));
|
||||
}
|
||||
template <typename T> bool collide(const Triangle<T>& t, const Vector<T, 2>& p) {
|
||||
return collide(p, t);
|
||||
}
|
||||
template <typename T> bool collide(const Vector<T, 2>& p, const AABB<T>& a) {
|
||||
return (p.x() > a.min.x() && p.x() < a.max.x() && p.y() > a.min.y() && p.y() < a.max.y());
|
||||
}
|
||||
template <typename T> bool collide(const AABB<T>& a, const Vector<T, 2>& p) {
|
||||
return collide(p, a);
|
||||
}
|
||||
template <typename T> bool collide(const Vector<T, 2>& p, const Circle<T>& a) {
|
||||
return vec_distance(p, a.pos) <= a.radius;
|
||||
}
|
||||
template <typename T> bool collide(const Circle<T>& a, const Vector<T, 2>& p) {
|
||||
return collide(p, a);
|
||||
}
|
||||
|
||||
template <typename T> bool collide(const Circle<T>& c1, const Circle<T>& c2) {
|
||||
return vec_distance(c1.pos, c2.pos) <= (c1.radius + c2.radius);
|
||||
}
|
||||
|
||||
// https://gamedev.stackexchange.com/a/178154
|
||||
template <typename T> bool collide(const Circle<T>& c, const AABB<T>& a) {
|
||||
if(!collide(get_bounds(c), a))
|
||||
return false;
|
||||
Vector<T, 2> aabbCenter = a.center();
|
||||
Vector<T, 2> aabbBounds = aabbCenter - a.min;
|
||||
Vector<T, 2> distance = c.pos - aabbCenter;
|
||||
Vector<T, 2> clampDst = cwise_vec_clamp(distance, (-aabbBounds).eval(), aabbBounds);
|
||||
Vector<T, 2> closestPoint = aabbCenter + clampDst;
|
||||
return vec_distance(closestPoint, c.pos) <= c.radius;
|
||||
}
|
||||
template <typename T> bool collide(const AABB<T>& a, const Circle<T>& c) {
|
||||
return collide(c, a);
|
||||
}
|
||||
|
||||
template <typename T> bool collide(const AABB<T>& a1, const AABB<T>& a2) {
|
||||
return a1.min.x() < a2.max.x() && a1.max.x() > a2.min.x() &&
|
||||
a1.min.y() < a2.max.y() && a1.max.y() > a2.min.y();
|
||||
}
|
||||
|
||||
template <typename T> bool collide(const Circle<T>& c, const Triangle<T>& t) {
|
||||
if(!collide(get_bounds(c), get_bounds(t)))
|
||||
return false;
|
||||
return collide(t.p[0], c) ||
|
||||
collide(t.p[1], c) ||
|
||||
collide(t.p[2], c) ||
|
||||
collide(c.pos, t) ||
|
||||
collision_circle_line_segment(c.pos, c.radius, t.p[0], t.p[1]) ||
|
||||
collision_circle_line_segment(c.pos, c.radius, t.p[1], t.p[2]) ||
|
||||
collision_circle_line_segment(c.pos, c.radius, t.p[2], t.p[0]);
|
||||
}
|
||||
template <typename T> bool collide(const Triangle<T>& t, const Circle<T>& c) {
|
||||
return collide(c, t);
|
||||
}
|
||||
|
||||
template <typename T> bool collide(const AABB<T>& a, const Triangle<T>& t) {
|
||||
if(!collide(a, get_bounds(t)))
|
||||
return false;
|
||||
return collide(t.p[0], a) ||
|
||||
collide(t.p[1], a) ||
|
||||
collide(t.p[2], a) ||
|
||||
collide(a.min, t) ||
|
||||
collide(a.max, t) ||
|
||||
collide(a.bottom_left(), t) ||
|
||||
collide(a.top_right(), t) ||
|
||||
is_collision_line_segment_line_segment(t.p[0], t.p[1], a.min, a.top_right()) ||
|
||||
is_collision_line_segment_line_segment(t.p[0], t.p[1], a.min, a.bottom_left()) ||
|
||||
is_collision_line_segment_line_segment(t.p[0], t.p[1], a.max, a.top_right()) ||
|
||||
is_collision_line_segment_line_segment(t.p[0], t.p[1], a.max, a.bottom_left()) ||
|
||||
is_collision_line_segment_line_segment(t.p[0], t.p[2], a.min, a.top_right()) ||
|
||||
is_collision_line_segment_line_segment(t.p[0], t.p[2], a.min, a.bottom_left()) ||
|
||||
is_collision_line_segment_line_segment(t.p[0], t.p[2], a.max, a.top_right()) ||
|
||||
is_collision_line_segment_line_segment(t.p[0], t.p[2], a.max, a.bottom_left()) ||
|
||||
is_collision_line_segment_line_segment(t.p[2], t.p[1], a.min, a.top_right()) ||
|
||||
is_collision_line_segment_line_segment(t.p[2], t.p[1], a.min, a.bottom_left()) ||
|
||||
is_collision_line_segment_line_segment(t.p[2], t.p[1], a.max, a.top_right()) ||
|
||||
is_collision_line_segment_line_segment(t.p[2], t.p[1], a.max, a.bottom_left());
|
||||
}
|
||||
template <typename T> bool collide(const Triangle<T>& t, const AABB<T>& a) {
|
||||
return collide(a, t);
|
||||
}
|
||||
|
||||
template <typename T> bool collide(const Triangle<T>& t1, const Triangle<T>& t2) {
|
||||
//return collide(get_bounds(t1), get_bounds(t2));
|
||||
if(!collide(get_bounds(t1), get_bounds(t2)))
|
||||
return false;
|
||||
return collide(t1.p[0], t2) ||
|
||||
collide(t1.p[1], t2) ||
|
||||
collide(t1.p[2], t2) ||
|
||||
collide(t2.p[0], t1) ||
|
||||
collide(t2.p[1], t1) ||
|
||||
collide(t2.p[2], t1) ||
|
||||
is_collision_line_segment_line_segment(t1.p[0], t1.p[1], t2.p[0], t2.p[1]) ||
|
||||
is_collision_line_segment_line_segment(t1.p[0], t1.p[1], t2.p[1], t2.p[2]) ||
|
||||
is_collision_line_segment_line_segment(t1.p[0], t1.p[1], t2.p[2], t2.p[0]) ||
|
||||
is_collision_line_segment_line_segment(t1.p[0], t1.p[2], t2.p[0], t2.p[1]) ||
|
||||
is_collision_line_segment_line_segment(t1.p[0], t1.p[2], t2.p[1], t2.p[2]) ||
|
||||
is_collision_line_segment_line_segment(t1.p[0], t1.p[2], t2.p[2], t2.p[0]) ||
|
||||
is_collision_line_segment_line_segment(t1.p[2], t1.p[1], t2.p[0], t2.p[1]) ||
|
||||
is_collision_line_segment_line_segment(t1.p[2], t1.p[1], t2.p[1], t2.p[2]) ||
|
||||
is_collision_line_segment_line_segment(t1.p[2], t1.p[1], t2.p[2], t2.p[0]);
|
||||
}
|
||||
|
||||
template <typename Collection, typename Collider> bool collide(const Collection& collection, const Collider& c) {
|
||||
for(auto& o : collection.circle)
|
||||
if(collide(c, o))
|
||||
return true;
|
||||
for(auto& o : collection.aabb)
|
||||
if(collide(c, o))
|
||||
return true;
|
||||
for(auto& o : collection.triangle)
|
||||
if(collide(c, o))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T> class BVHContainer {
|
||||
public:
|
||||
typedef T UnderlyingType;
|
||||
|
||||
std::vector<BVHContainer<T>> children;
|
||||
ColliderCollection<T> objects;
|
||||
|
||||
// NOTE: BVH TRANSFORMATION LEADS TO A BROKEN BVH
|
||||
//void transform(const std::function<Vector<T, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<T(const T&)>& transformLinearFunc) {
|
||||
// objects.transform(transformFunc, transformLinearFunc);
|
||||
// for(auto& a : children)
|
||||
// a.transform(transformFunc, transformLinearFunc);
|
||||
//}
|
||||
|
||||
void clear() {
|
||||
children.clear();
|
||||
objects.clear();
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return children.empty() && objects.empty();
|
||||
}
|
||||
|
||||
unsigned assign_quad_to_point(const Vector<T, 2>& point, const Vector<T, 2>& origin) const {
|
||||
bool negativeX = point.x() <= origin.x();
|
||||
bool negativeY = point.y() <= origin.y();
|
||||
|
||||
if(negativeX && negativeY)
|
||||
return 0;
|
||||
else if(negativeX && !negativeY)
|
||||
return 1;
|
||||
else if(!negativeX && !negativeY)
|
||||
return 2;
|
||||
else
|
||||
return 3;
|
||||
}
|
||||
|
||||
void calculate_bvh_recursive(ColliderCollection<T>& bvhObjects) {
|
||||
bvhObjects.recalculate_bounds();
|
||||
|
||||
objects.bounds = bvhObjects.bounds;
|
||||
|
||||
if(bvhObjects.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(bvhObjects.size() <= 2) {
|
||||
objects = bvhObjects;
|
||||
return;
|
||||
}
|
||||
|
||||
ColliderCollection<T> parts[4];
|
||||
Vector<T, 2> halfwayPoint = bvhObjects.bounds.center();
|
||||
|
||||
for(auto& o : bvhObjects.aabb)
|
||||
parts[assign_quad_to_point(get_bounds(o).center(), halfwayPoint)].aabb.emplace_back(o);
|
||||
for(auto& o : bvhObjects.circle)
|
||||
parts[assign_quad_to_point(get_bounds(o).center(), halfwayPoint)].circle.emplace_back(o);
|
||||
for(auto& o : bvhObjects.triangle)
|
||||
parts[assign_quad_to_point(get_bounds(o).center(), halfwayPoint)].triangle.emplace_back(o);
|
||||
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
if(parts[i].empty())
|
||||
continue;
|
||||
else if(parts[i].size() == bvhObjects.size()) {
|
||||
objects = bvhObjects;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
children.emplace_back();
|
||||
children.back().calculate_bvh_recursive(parts[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Collider> bool is_collide(const Collider& collider) const {
|
||||
if(!collide(get_bounds(collider), get_bounds(objects)))
|
||||
return false;
|
||||
if(collide(objects, collider))
|
||||
return true;
|
||||
for(const auto& c : children)
|
||||
if(c.is_collide(collider))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename BVH> bool is_collide_other_bvh(const BVH& bvh) const {
|
||||
if(!collide(get_bounds(objects), get_bounds(bvh.objects)))
|
||||
return false;
|
||||
if(is_collide(bvh.objects))
|
||||
return true;
|
||||
if(bvh.is_collide(objects))
|
||||
return true;
|
||||
for(const auto& c : children) {
|
||||
for(const auto& d : bvh.children) {
|
||||
if(c.is_collide_other_bvh(d))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
16
include/Helpers/Serializers.cpp
Normal file
16
include/Helpers/Serializers.cpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#include "Serializers.hpp"
|
||||
|
||||
void to_json(nlohmann::json& j, const SkColor4f& d) {
|
||||
j = nlohmann::json::array();
|
||||
j.emplace_back(d.fR);
|
||||
j.emplace_back(d.fG);
|
||||
j.emplace_back(d.fB);
|
||||
j.emplace_back(d.fA);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, SkColor4f& d) {
|
||||
j.at(0).template get_to<float>(d.fR);
|
||||
j.at(1).template get_to<float>(d.fG);
|
||||
j.at(2).template get_to<float>(d.fB);
|
||||
j.at(3).template get_to<float>(d.fA);
|
||||
}
|
||||
107
include/Helpers/Serializers.hpp
Normal file
107
include/Helpers/Serializers.hpp
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#pragma once
|
||||
#include <Eigen/Dense>
|
||||
#include <cereal/archives/portable_binary.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
#include "FixedPoint.hpp"
|
||||
|
||||
#include <include/core/SkColor.h>
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
// https://stackoverflow.com/questions/22884216/serializing-eigenmatrix-using-cereal-library (with some modifications using constexpr)
|
||||
template <class Archive, class Derived> inline
|
||||
typename std::enable_if<traits::is_output_serializable<BinaryData<typename Derived::Scalar>, Archive>::value, void>::type
|
||||
save(Archive& ar, Eigen::PlainObjectBase<Derived> const & m){
|
||||
if constexpr(std::is_same_v<typename Derived::Scalar, WorldScalar>) {
|
||||
for(int i = 0; i < m.rows(); i++)
|
||||
ar(m.data()[i]);
|
||||
}
|
||||
else {
|
||||
typedef Eigen::PlainObjectBase<Derived> ArrT;
|
||||
if constexpr(ArrT::RowsAtCompileTime==Eigen::Dynamic)
|
||||
ar(static_cast<uint32_t>(m.rows()));
|
||||
if constexpr(ArrT::ColsAtCompileTime==Eigen::Dynamic)
|
||||
ar(static_cast<uint32_t>(m.cols()));
|
||||
ar(binary_data(m.data(),static_cast<uint64_t>(m.rows()*m.cols()*sizeof(typename Derived::Scalar))));
|
||||
}
|
||||
}
|
||||
|
||||
template <class Archive, class Derived> inline
|
||||
typename std::enable_if<traits::is_input_serializable<BinaryData<typename Derived::Scalar>, Archive>::value, void>::type
|
||||
load(Archive& ar, Eigen::PlainObjectBase<Derived>& m) {
|
||||
if constexpr(std::is_same_v<typename Derived::Scalar, WorldScalar>) {
|
||||
for(int i = 0; i < m.rows(); i++)
|
||||
ar(m.data()[i]);
|
||||
}
|
||||
else {
|
||||
typedef Eigen::PlainObjectBase<Derived> ArrT;
|
||||
const Eigen::Index rows=ArrT::RowsAtCompileTime, cols=ArrT::ColsAtCompileTime;
|
||||
uint32_t r = rows;
|
||||
uint32_t c = cols;
|
||||
|
||||
if constexpr(rows==Eigen::Dynamic)
|
||||
ar(r);
|
||||
if constexpr(cols==Eigen::Dynamic)
|
||||
ar(c);
|
||||
if constexpr(cols==Eigen::Dynamic || rows == Eigen::Dynamic)
|
||||
m.resize(r, c);
|
||||
ar(binary_data(m.data(),static_cast<uint64_t>(m.rows()*m.cols()*sizeof(typename Derived::Scalar))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Eigen {
|
||||
// Some issue in nlohmann json prevents us from using Eigen::PlainObjectBase, so we have to specify the matrix class instead
|
||||
template<typename Scalar, int Rows, int Cols> void to_json(nlohmann::json& j, const Eigen::Matrix<Scalar, Rows, Cols>& m) {
|
||||
const Eigen::Index rows=m.RowsAtCompileTime, cols=m.ColsAtCompileTime;
|
||||
|
||||
j = nlohmann::json::array();
|
||||
|
||||
if(rows != Eigen::Dynamic && rows == 1) {
|
||||
for(Eigen::Index k = 0; k < m.cols(); k++)
|
||||
j.emplace_back(m(0, k));
|
||||
}
|
||||
else if(cols != Eigen::Dynamic && cols == 1) {
|
||||
for(Eigen::Index k = 0; k < m.rows(); k++)
|
||||
j.emplace_back(m(k, 0));
|
||||
}
|
||||
else {
|
||||
for(size_t i = 0; i < m.rows(); i++) {
|
||||
nlohmann::json a = nlohmann::json::array();
|
||||
for(Eigen::Index k = 0; k < m.cols(); k++) {
|
||||
a.emplace_back(m(i, k));
|
||||
}
|
||||
j.emplace_back(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Scalar, int Rows, int Cols> void from_json(const nlohmann::json& j, Eigen::Matrix<Scalar, Rows, Cols>& m) {
|
||||
const Eigen::Index rows=m.RowsAtCompileTime, cols=m.ColsAtCompileTime;
|
||||
|
||||
if(rows != Eigen::Dynamic && rows == 1) {
|
||||
m.resize(1, j.size());
|
||||
for(Eigen::Index k = 0; k < m.cols(); k++)
|
||||
m(0, k) = j[k];
|
||||
}
|
||||
else if(cols != Eigen::Dynamic && cols == 1) {
|
||||
m.resize(j.size(), 1);
|
||||
for(Eigen::Index k = 0; k < m.rows(); k++)
|
||||
m(k, 0) = j[k];
|
||||
}
|
||||
else {
|
||||
if(j.size() != 0 && j[0].size() != 0) {
|
||||
m.resize(j.size(), j[0].size());
|
||||
for(Eigen::Index i = 0; i < m.rows(); i++)
|
||||
for(Eigen::Index k = 0; k < m.cols(); k++)
|
||||
m(i, k) = j[i][k];
|
||||
}
|
||||
else
|
||||
m.resize(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const SkColor4f& d);
|
||||
void from_json(const nlohmann::json& j, SkColor4f& d);
|
||||
67
include/Helpers/StringHelpers.cpp
Normal file
67
include/Helpers/StringHelpers.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#include "StringHelpers.hpp"
|
||||
#include <fstream>
|
||||
|
||||
std::vector<std::string> split_string_by_token(std::string str, std::string token) {
|
||||
// slightly modified from: https://stackoverflow.com/a/46943631
|
||||
std::vector<std::string> toRet;
|
||||
while(!str.empty()) {
|
||||
size_t index = str.find(token);
|
||||
if(index != std::string::npos) {
|
||||
toRet.emplace_back(str.substr(0, index));
|
||||
str = str.substr(index + token.size());
|
||||
if(str.empty())
|
||||
toRet.emplace_back(str);
|
||||
}
|
||||
else {
|
||||
toRet.emplace_back(str);
|
||||
return toRet;
|
||||
}
|
||||
}
|
||||
return toRet;
|
||||
}
|
||||
|
||||
uint8_t ascii_hex_char_to_number_no_checks(char asciiHex) {
|
||||
if(asciiHex <= '9')
|
||||
return static_cast<uint8_t>(asciiHex - '0');
|
||||
else
|
||||
return static_cast<uint8_t>(asciiHex - 'A');
|
||||
}
|
||||
|
||||
std::string byte_vector_to_hex_str(const std::vector<uint8_t>& byteVec) {
|
||||
static constexpr char charList[] = "0123456789ABCDEF";
|
||||
|
||||
std::string ret;
|
||||
ret.reserve(byteVec.size() * 2);
|
||||
|
||||
for(uint8_t b : byteVec) {
|
||||
ret.push_back(charList[b >> 4]);
|
||||
ret.push_back(charList[b & 0x0F]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string read_file_to_string(const std::filesystem::path& filePath) {
|
||||
std::string toRet;
|
||||
// https://nullptr.org/cpp-read-file-into-string/
|
||||
std::ifstream file(filePath, std::ios::in | std::ios::binary | std::ios::ate);
|
||||
|
||||
if(!file.is_open())
|
||||
throw std::runtime_error("[read_file_to_string] Could not open file");
|
||||
|
||||
size_t resourcesize;
|
||||
|
||||
auto tellgResult = file.tellg();
|
||||
|
||||
if(tellgResult == -1)
|
||||
throw std::runtime_error("[read_file_to_string] tellg failed for file");
|
||||
|
||||
resourcesize = static_cast<size_t>(tellgResult);
|
||||
|
||||
file.seekg(0, std::ios_base::beg);
|
||||
toRet.resize(resourcesize);
|
||||
file.read(toRet.data(), resourcesize);
|
||||
file.close();
|
||||
|
||||
return toRet;
|
||||
}
|
||||
14
include/Helpers/StringHelpers.hpp
Normal file
14
include/Helpers/StringHelpers.hpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
|
||||
std::vector<std::string> split_string_by_token(std::string str, std::string token);
|
||||
|
||||
// Hex character must be capitalized if it is a letter
|
||||
uint8_t ascii_hex_char_to_number_no_checks(char asciiHex);
|
||||
|
||||
std::string byte_vector_to_hex_str(const std::vector<uint8_t>& byteVec);
|
||||
|
||||
std::string read_file_to_string(const std::filesystem::path& filePath);
|
||||
Loading…
Add table
Add a link
Reference in a new issue