Add srb2::math, fixed-point, vector, line and slope formula classes

- srb2::math::Fixed
  - Operator overloads for FixedMul and FixedDiv
  - Implicit conversion between fixed_t, Fixed and
    floating-point types
- srb2::math::Vec2
  - Template to any type
  - Operator overloads for arithmetic operations
  - Convertible between different types
- srb2::math::LineSegment
  - Template to any type
  - Holds two Vec2 instances
  - Sorting methods and vertical/horizontal test
- srb2::math::LineEquation
  - Slope formula from LineSegment
  - y method to find y from x
  - Intersect algorithm
  - Fixed-point specialization to avoid overflows
- srb2::math::LineEquationX
  - Inherits LineEquation
  - x method to find x from y
This commit is contained in:
James R 2023-11-09 22:46:57 -08:00
parent 6686215330
commit 37f2384229
5 changed files with 370 additions and 0 deletions

107
src/math/fixed.hpp Normal file
View file

@ -0,0 +1,107 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_fixed_hpp
#define math_fixed_hpp
#include <type_traits>
#include "traits.hpp"
#include "../m_fixed.h"
namespace srb2::math
{
struct Fixed
{
static Fixed copysign(fixed_t x, fixed_t y) { return (x < 0) != (y < 0) ? -x : x; }
static Fixed hypot(fixed_t x, fixed_t y) { return FixedHypot(x, y); }
constexpr Fixed() : val_(0) {}
constexpr Fixed(fixed_t val) : val_(val) {}
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
Fixed(T val) : val_(FloatToFixed(val)) {}
Fixed(const Fixed& b) = default;
Fixed& operator=(const Fixed& b) = default;
fixed_t value() const { return val_; }
int sign() const { return val_ < 0 ? -1 : 1; }
operator fixed_t() const { return val_; }
explicit operator float() const { return FixedToFloat(val_); }
Fixed& operator+=(const Fixed& b)
{
val_ += b.val_;
return *this;
}
Fixed& operator-=(const Fixed& b)
{
val_ -= b.val_;
return *this;
}
Fixed& operator*=(const Fixed& b)
{
val_ = FixedMul(val_, b.val_);
return *this;
}
Fixed& operator/=(const Fixed& b)
{
val_ = FixedDiv(val_, b.val_);
return *this;
}
Fixed operator-() const { return -val_; }
#define X(op) \
template <typename T> \
Fixed operator op(const T& b) const { return val_ op b; } \
Fixed operator op(const Fixed& b) const \
{ \
Fixed f{val_};\
f op##= b;\
return f;\
} \
template <typename T> \
Fixed& operator op##=(const T& b) \
{ \
val_ op##= b; \
return *this; \
}
X(+)
X(-)
X(*)
X(/)
#undef X
private:
fixed_t val_;
};
template <>
struct Traits<Fixed>
{
static constexpr Fixed kZero = 0;
static constexpr Fixed kUnit = FRACUNIT;
static constexpr auto copysign = Fixed::copysign;
static constexpr auto hypot = Fixed::hypot;
};
}; // namespace srb2::math
#endif/*math_fixed_hpp*/

102
src/math/line_equation.hpp Normal file
View file

@ -0,0 +1,102 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_line_equation_hpp
#define math_line_equation_hpp
#include "fixed.hpp"
#include "line_segment.hpp"
#include "vec.hpp"
namespace srb2::math
{
template <typename T>
struct LineEquation
{
using vec2 = Vec2<T>;
using line_segment = LineSegment<T>;
// Fixed-point: shift value by this amount during
// multiplications and divisions to avoid overflows.
static constexpr std::enable_if_t<std::is_same_v<T, Fixed>, fixed_t> kF = 1024; // fixed_t, not Fixed
LineEquation() {}
LineEquation(const vec2& p, const vec2& d) : d_(d), m_(d.y / d.x), b_(p.y - (p.x * m())) {}
LineEquation(const line_segment& l) : LineEquation(l.a, l.b - l.a) {}
const vec2& d() const { return d_; }
T m() const { return m_; }
T b() const { return b_; }
T y(T x) const { return (m() * x) + b(); }
vec2 intersect(const LineEquation& q) const
{
T x = (b() - q.b()) / (q.m() - m());
return {x, y(x)};
}
protected:
vec2 d_{};
T m_{}, b_{};
};
template <>
inline LineEquation<Fixed>::LineEquation(const vec2& p, const vec2& d) :
d_(d), m_((d.y / d.x) / kF), b_((p.y / kF) - (p.x * m_))
{
}
template <>
inline Fixed LineEquation<Fixed>::m() const
{
return m_ * kF;
}
template <>
inline Fixed LineEquation<Fixed>::b() const
{
return b_ * kF;
}
template <>
inline Fixed LineEquation<Fixed>::y(Fixed x) const
{
return ((m_ * x) + b_) * kF;
}
template <>
inline LineEquation<Fixed>::vec2 LineEquation<Fixed>::intersect(const LineEquation& q) const
{
Fixed x = ((b_ - q.b_) / ((q.m_ - m_) * kF)) * kF;
return {x, y(x)};
}
template <typename T>
struct LineEquationX : LineEquation<T>
{
T x(T y) const { return (y - LineEquation<T>::b()) / LineEquation<T>::m(); }
};
template <>
struct LineEquationX<Fixed> : LineEquation<Fixed>
{
LineEquationX() {}
LineEquationX(const vec2& p, const vec2& d) : LineEquation(p, d), w_((d.x / d.y) / kF), a_((p.x / kF) - (p.y * w_)) {}
LineEquationX(const line_segment& l) : LineEquationX(l.a, l.b - l.a) {}
Fixed x(Fixed y) const { return ((w_ * y) + a_) * kF; }
protected:
Fixed w_{}, a_{};
};
}; // namespace srb2::math
#endif/*math_line_equation_hpp*/

43
src/math/line_segment.hpp Normal file
View file

@ -0,0 +1,43 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_line_segment_hpp
#define math_line_segment_hpp
#include <algorithm>
#include <utility>
#include "vec.hpp"
namespace srb2::math
{
template <typename T>
struct LineSegment
{
using vec2 = Vec2<T>;
using view = std::pair<const vec2&, const vec2&>;
vec2 a, b;
LineSegment(vec2 a_, vec2 b_) : a(a_), b(b_) {}
template <typename U>
LineSegment(const LineSegment<U>& b) : LineSegment(b.a, b.b) {}
bool horizontal() const { return a.y == b.y; }
bool vertical() const { return a.x == b.x; }
view by_x() const { return std::minmax(a, b, [](auto& a, auto& b) { return a.x < b.x; }); }
view by_y() const { return std::minmax(a, b, [](auto& a, auto& b) { return a.y < b.y; }); }
};
}; // namespace srb2
#endif/*math_line_segment_hpp*/

34
src/math/traits.hpp Normal file
View file

@ -0,0 +1,34 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_traits_hpp
#define math_traits_hpp
#include <cmath>
#include <type_traits>
namespace srb2::math
{
template <typename T, typename = void>
struct Traits;
template <typename T>
struct Traits<T, std::enable_if_t<std::is_floating_point_v<T>>>
{
static constexpr T kZero = 0.0;
static constexpr T kUnit = 1.0;
static T copysign(T x, T y) { return std::copysign(x, y); }
static T hypot(T x, T y) { return std::hypot(x, y); }
};
}; // namespace srb2::math
#endif/*math_traits_hpp*/

84
src/math/vec.hpp Normal file
View file

@ -0,0 +1,84 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef math_vec_hpp
#define math_vec_hpp
#include <type_traits>
#include "traits.hpp"
namespace srb2::math
{
template <typename T>
struct Vec2
{
T x, y;
Vec2() : x{}, y{} {}
Vec2(T x_, T y_) : x(x_), y(y_) {}
Vec2(T z) : x(z), y(z) {}
template <typename U>
Vec2(const Vec2<U>& b) : Vec2(b.x, b.y) {}
T magnitude() const { return Traits<T>::hypot(x, y); }
Vec2 normal() const { return {-y, x}; }
#define X(op) \
Vec2& operator op##=(const Vec2& b) \
{ \
x op##= b.x; \
y op##= b.y; \
return *this; \
} \
Vec2 operator op(const Vec2& b) const { return Vec2(x op b.x, y op b.y); } \
X(+)
X(-)
X(*)
X(/)
#undef X
Vec2 operator-() const { return Vec2(-x, -y); }
};
template <typename>
struct is_vec2 : std::false_type {};
template <typename T>
struct is_vec2<Vec2<T>> : std::true_type {};
template <typename T>
inline constexpr bool is_vec2_v = is_vec2<T>::value;
#define X(op) \
template <typename T, typename U, std::enable_if_t<!is_vec2_v<T>, bool> = true> \
Vec2<T> operator op(const T& a, const Vec2<U>& b) \
{ \
return Vec2 {a} op Vec2<T> {b}; \
} \
template <typename T, typename U, std::enable_if_t<!is_vec2_v<U>, bool> = true> \
Vec2<U> operator op(const Vec2<T>& a, const U& b) \
{ \
return Vec2<U> {a} op Vec2 {b}; \
} \
X(+)
X(-)
X(*)
X(/)
#undef X
}; // namespace srb2::math
#endif/*math_vec_hpp*/