diff --git a/README.md b/README.md index 9af3fe9..3130acb 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ In progress... ## Codemap -- [`utility`](robocin/utility): a collection of utility and helper code. +- [`geometry`](robocin/geometry): a collection of geometric classes; +- [`utility`](robocin/utility): a collection of utility and helper code; diff --git a/robocin/CMakeLists.txt b/robocin/CMakeLists.txt index f8474db..da17b0f 100644 --- a/robocin/CMakeLists.txt +++ b/robocin/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(utility) +add_subdirectory(geometry) diff --git a/robocin/geometry/CMakeLists.txt b/robocin/geometry/CMakeLists.txt new file mode 100644 index 0000000..09bc3ac --- /dev/null +++ b/robocin/geometry/CMakeLists.txt @@ -0,0 +1,12 @@ +robocin_cpp_library( + NAME point2d + HDRS point2d.h + SRCS point2d.cpp +) + +robocin_cpp_test( + NAME point2d_test + HDRS ../utility/internal/test/epsilon_injector.h + SRCS point2d_test.cpp + DEPS point2d +) diff --git a/robocin/geometry/README.md b/robocin/geometry/README.md new file mode 100644 index 0000000..25bb58a --- /dev/null +++ b/robocin/geometry/README.md @@ -0,0 +1,203 @@ +# geometry + +A collection of geometric classes. + +## Table of Contents + +- [Point2D](#point2d) + + + +## [`Point2D`](point2d.h) + +The `Point2D` templated struct represents a 2-dimensional point (vector) in a Cartesian coordinate system. It provides +various methods and operators for manipulating and performing calculations with 2d-points. + +### Member Types + +- `value_type`: The type of the point's coordinates; +- `reference`: A reference to `value_type`; +- `size_type`: An unsigned integer type used for size and indexing; +- `iterator`: An iterator type for iterating over mutable `Point2D` objects; +- `const_iterator`: An iterator type for iterating over const `Point2D` objects; +- `reverse_iterator`: A reverse iterator type for reverse iteration over mutable `Point2D` objects; +- `const_reverse_iterator`: A reverse iterator type for reverse iteration over const `Point2D` objects; + +### Static Constructors + +- `static Point2D origin()`: Returns a point representing the origin (0, 0); +- `static Point2D fromPolar(value_type angle) requires(std::floating_point)`: Creates a point from polar + coordinates with the given angle; +- `static Point2D fromPolar(value_type angle, value_type length) requires(std::floating_point)`: Creates a + point from polar coordinates with the given angle and length; + +### Constructors + +- `Point2D()`: Default constructor. Initializes the point with coordinates (0, 0); +- `Point2D(const Point2D& point)`: Creates a new point by copying the coordinates of another point; +- `Point2D(Point2D&& point)`: Creates a new point by moving the coordinates of another point; +- `Point2D(value_type x, value_type y)`: Initializes the point with the specified coordinates; +- `explicit Point2D(const OtherStructPoint auto& point)`: Initializes the point from coordinates (`point.x`, `point.y`); +- `explicit Point2D(const OtherClassPoint auto& point)`: Initializes the point from + coordinates (`point.x()`, `point.y()`); +- `explicit Point2D(const std::pair& pair)`: Constructor. Initializes the point with the coordinates from + a `std::pair`; + +### Assignment Operators + +- `constexpr Point2D& operator=(const Point2D& other) = default`: Copy assignment operator. Assigns the coordinates of + another point to this point; +- `constexpr Point2D& operator=(Point2D&& other) noexcept = default`: Move assignment operator. Moves the coordinates of + another point to this point; + +### Validators + +- `bool isNull() const`: Checks if the point (vector) is null (coordinates are both zero). Returns `true` if the point + is null, `false` otherwise; + +### Arithmetic-Assignment Operators + +- `Point2D& operator+=(const Point2D& other)`: Adds the coordinates of another point to this point and updates its + value. Returns a reference to the modified point; +- `Point2D& operator-=(const Point2D& other)`: Subtracts the coordinates of another point from this point and updates + its value. Returns a reference to the modified point; +- `Point2D& operator*=(value_type factor)`: Multiplies the coordinates of this point by a scalar factor and updates its + value. Returns a reference to the modified point; +- `Point2D& operator/=(value_type factor)`: Divides the coordinates of this point by a scalar factor and updates its + value. Returns a reference to the modified point; + +### Arithmetic Operators + +- `Point2D operator+(const Point2D& other) const`: Addition operator that returns the sum of two points; +- `Point2D operator-(const Point2D& other) const`: Subtraction operator that returns the difference between two points; +- `Point2D operator*(value_type factor) const`: Multiplication operator that scales the point by a factor; +- `Point2D operator/(value_type factor) const`: Division operator that scales the point by the inverse of a factor; + +### Arithmetic Friend Operators + +- `friend Point2D operator*(value_type factor, const Point2D& point)`: Friend operator that allows multiplication of a + point by a factor in the reverse order; + +### Sign Operators + +- `Point2D operator+() const`: Unary plus operator that returns the point itself; +- `Point2D operator-() const`: Unary minus operator that returns the negation of the point; + +### Comparison Operators + +- `bool operator==(const Point2D& other) const`: Equality operator that checks if two points are equal; +- `auto operator<=>(const Point2D& other) const`: Three-way comparison operator that compares + two points and returns their relative ordering; + +### Swap + +- `void swap(Point2D& other) noexcept`: Swaps the contents of the current point with another point; + +### Geometry + +- `value_type dot(const Point2D& other) const`: Computes the dot product of the current point and another point; +- `value_type cross(const Point2D& other) const`: Computes the cross product of the current point and another point; +- `value_type manhattanLength() const`: Computes the Manhattan distance between the origin and the current point; +- `value_type manhattanDistTo(const Point2D& other) const`: Computes the Manhattan distance between the current point + and another point; +- `value_type lengthSquared() const`: Computes the square of the Euclidean length of the current point; +- `std::floating_point auto length() const`: Computes the Euclidean length of the current point; +- `std::floating_point auto norm() const`: Computes the Euclidean length of the current point; +- `value_type distSquaredTo(const Point2D& other) const`: Computes the square of the Euclidean distance between the + current point and another point; +- `std::floating_point auto distTo(const Point2D& other) const`: Computes the Euclidean distance between the current + point and another point; +- `std::floating_point auto angle() const`: Computes the angle (in radians) [-PI , +PI] between the positive x-axis and + the origin to the current point; +- `std::floating_point auto angleTo(const Point2D& other) const`: Computes the angle (in radians) [-PI , +PI] between + the origin to the current point and the origin to another point; + +### Rotations + +- `void rotateCW90() &`: Rotates the current point 90 degrees clockwise; +- `Point2D rotatedCW90() &&`: Returns a new point obtained by rotating the current point 90 degrees clockwise; +- `Point2D rotatedCW90() const&`: Returns a new point obtained by rotating a copy of the current point 90 degrees + clockwise; + + +- `void rotateCCW90() &`: Rotates the current point 90 degrees counterclockwise; +- `Point2D rotatedCCW90() &&`: Returns a new point obtained by rotating the current point 90 degrees counterclockwise; +- `Point2D rotatedCCW90() const&`: Returns a new point obtained by rotating a copy of the current point 90 degrees + counterclockwise; + + +- `void rotateCW(value_type t) &`: Rotates the current point clockwise by a specified angle `t` (in radians); +- `Point2D rotatedCW(value_type t) &&`: Returns a new point obtained by rotating the current point clockwise by a + specified angle `t` (in radians); +- `Point2D rotatedCW(value_type t) const&`: Returns a new point obtained by rotating a copy of the current point + clockwise by a specified angle `t` (in radians); + + +- `void rotateCCW(value_type t) &`: Rotates the current point counterclockwise by a specified angle `t` (in radians); +- `Point2D rotatedCCW(value_type t) &&`: Returns a new point obtained by rotating the current point counterclockwise by + a specified angle `t` (in radians); +- `Point2D rotatedCCW(value_type t) const&`: Returns a new point obtained by rotating a copy of the current point + counterclockwise by a specified angle `t` (in radians); + +### Resizing and Normalization + +- `void resize(value_type t) &`: Resizes the current point by a factor `t`; +- `Point2D resized(value_type t) &&`: Returns a new point obtained by resizing the current point by a factor `t`; +- `Point2D resized(value_type t) const&`: Returns a new point obtained by resizing a copy of the current point by a + factor `t`; + +- `void normalize() &`: Normalizes the current point to have unit length. + +> **Note:** The normalization differs for real and integer numbers, being equivalent to `resize(1)` for real numbers, +> and the division of coordinates by their gcd for integer numbers. + +- `Point2D normalized() &&`: Returns a new point obtained by normalizing the current point to have unit length; +- `Point2D normalized() const&`: Returns a new point obtained by normalizing a copy of the current point to have unit + length; + + +- `void axesNormalize() &`: Normalizes the current point to have coordinates of -1, 0, or 1; +- `Point2D axesNormalized() &&`: Returns a new point obtained by normalizing the current point to have coordinates of + -1, 0, or 1; +- `Point2D axesNormalized() const&`: Returns a new point obtained by normalizing a copy of the current point to have + coordinates of -1, 0, or 1; + +### Array-like + +- `static size_type size()`: Returns the size of the point (always 2); +- `reference operator[](size_type pos)`: Provides access to the elements of the point using the subscript operator; +- `value_type operator[](size_type pos) const`: Provides access to the elements of the point using the subscript + operator; + +### Iterators + +The following iterator methods provide support for iterating over the elements of the point: + +- `iterator begin() noexcept`; +- `const_iterator begin() const noexcept`; +- `iterator end() noexcept`; +- `const_iterator end() const noexcept`; +- `reverse_iterator rbegin() noexcept`; +- `const_reverse_iterator rbegin() const noexcept`; +- `reverse_iterator rend() noexcept`; +- `const_reverse_iterator rend() const noexcept`; +- `const_iterator cbegin() const noexcept`; +- `const_iterator cend() const noexcept`; +- `const_reverse_iterator crbegin() const noexcept`; +- `const_reverse_iterator crend() const noexcept`; + +### Input/Output Operators + +The following operators allow input and output of `Point2D` objects: + +- `std::istream& operator>>(std::istream& is, Point2D& point)`: Reads a `Point2D` object from an input stream. +- `std::ostream& operator<<(std::ostream& os, const Point2D& point)`: Writes a `Point2D` object to an output stream. + +### Deduction Guides + +The following deduction guides are provided: + +- `Point2D() -> Point2D`: Constructs a `Point2D` with `double` coordinates when no arguments are provided; +- `Point2D(T, U) -> Point2D>`: This deduction guide ensures that when constructing a `Point2D` + with different coordinate types `T` and `U`, the resulting `Point2D` type will have a coordinate type that is the + common type of `T` and `U`; diff --git a/robocin/geometry/internal/point2d_internal.h b/robocin/geometry/internal/point2d_internal.h new file mode 100644 index 0000000..a83e12f --- /dev/null +++ b/robocin/geometry/internal/point2d_internal.h @@ -0,0 +1,109 @@ +// +// Created by José Cruz on 24/02/23. +// Copyright (c) 2023 RobôCIn. +// + +#ifndef ROBOCIN_GEOMETRY_POINT2D_INTERNAL_H +#define ROBOCIN_GEOMETRY_POINT2D_INTERNAL_H + +#include +#include + +namespace robocin::point2d_internal { + +template +concept StructPoint = requires(PT point) { + point.x; + point.y; +}; + +template +concept ClassPoint = requires(PT point) { + point.x(); + point.y(); +}; + +template +class iterator { + using point_type = PT; + using point_pointer = point_type*; + + template + using dependent_const_t = std::conditional_t, const T, T>; + + public: + // NOLINTNEXTLINE(readability-identifier-naming) + using iterator_category = std::random_access_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = dependent_const_t; + using pointer = value_type*; + using reference = value_type&; + + // Constructors ---------------------------------------------------------------------------------- + constexpr iterator() = default; + constexpr iterator(const iterator&) = default; + constexpr iterator(iterator&&) noexcept = default; + constexpr iterator(point_pointer ptr, difference_type index) : ptr_(ptr), index_(index) {} + + // Assignment operators -------------------------------------------------------------------------- + constexpr iterator& operator=(const iterator&) = default; + constexpr iterator& operator=(iterator&&) noexcept = default; + + // Destructor ------------------------------------------------------------------------------------ + constexpr ~iterator() = default; + + // Dereference operators ------------------------------------------------------------------------- + constexpr reference operator*() const { + switch (index_) { + case 0: return ptr_->x; + case 1: return ptr_->y; + default: throw std::out_of_range("Point2D::iterator operator*: index out of range."); + } + } + + constexpr reference operator[](difference_type n) const { + switch (index_ + n) { + case 0: return ptr_->x; + case 1: return ptr_->y; + default: throw std::out_of_range("Point2D::iterator operator[]: index out of range."); + } + } + + // Arithmetic-assignment operators --------------------------------------------------------------- + constexpr iterator& operator+=(difference_type n) { return index_ += n, *this; } + constexpr iterator& operator-=(difference_type n) { return index_ -= n, *this; } + + // Arithmetic operators -------------------------------------------------------------------------- + constexpr iterator operator+(difference_type n) const { return iterator(*this) += n; } + constexpr iterator operator-(difference_type n) const { return iterator(*this) -= n; } + + constexpr difference_type operator-(const iterator& other) const { return index_ - other.index_; } + + // Arithmetic friend operator -------------------------------------------------------------------- + friend constexpr iterator operator+(difference_type n, const iterator& it) { return it + n; } + + // Increment and decrement operators ------------------------------------------------------------- + constexpr iterator& operator++() { return ++index_, *this; } + constexpr iterator operator++(int) { // NOLINT(cert-dcl21-cpp) + iterator result = *this; + return ++index_, result; + } + + constexpr iterator& operator--() { return --index_, *this; } + constexpr iterator operator--(int) { // NOLINT(cert-dcl21-cpp) + iterator result = *this; + return --index_, result; + } + + // Comparison operators -------------------------------------------------------------------------- + inline constexpr bool operator==(const iterator& other) const = default; + inline constexpr auto operator<=>(const iterator& other) const = default; + + private: + point_pointer ptr_{nullptr}; + difference_type index_{0}; +}; + +} // namespace robocin::point2d_internal + +#endif // ROBOCIN_GEOMETRY_POINT2D_INTERNAL_H diff --git a/robocin/geometry/point2d.cpp b/robocin/geometry/point2d.cpp new file mode 100644 index 0000000..10fe031 --- /dev/null +++ b/robocin/geometry/point2d.cpp @@ -0,0 +1,17 @@ +// +// Created by José Cruz on 24/02/23. +// Copyright (c) 2023 RobôCIn. +// + +#include "robocin/geometry/point2d.h" + +namespace robocin { + +template struct Point2D; +template struct Point2D; +template struct Point2D; +template struct Point2D; +template struct Point2D; +template struct Point2D; + +} // namespace robocin diff --git a/robocin/geometry/point2d.h b/robocin/geometry/point2d.h new file mode 100644 index 0000000..53d952a --- /dev/null +++ b/robocin/geometry/point2d.h @@ -0,0 +1,352 @@ +// +// Created by José Cruz on 24/02/23. +// Copyright (c) 2023 RobôCIn. +// + +#ifndef ROBOCIN_GEOMETRY_POINT2D_H +#define ROBOCIN_GEOMETRY_POINT2D_H + +#include "robocin/geometry/internal/point2d_internal.h" +#include "robocin/utility/fuzzy_compare.h" + +#include +#include +#include +#include +#include + +namespace robocin { + +template +struct Point2D { + public: + // Member types ---------------------------------------------------------------------------------- + using value_type = T; + using reference = value_type&; + using size_type = std::size_t; + using iterator = point2d_internal::iterator; + using const_iterator = point2d_internal::iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // Friendships ----------------------------------------------------------------------------------- + template + friend struct Point2D; + + // Members --------------------------------------------------------------------------------------- + value_type x, y; + + // Static constructors --------------------------------------------------------------------------- + static consteval Point2D origin() { return Point2D(); } + static constexpr Point2D fromPolar(value_type angle) + requires(std::floating_point) + { + return Point2D(std::cos(angle), std::sin(angle)); + } + static constexpr Point2D fromPolar(value_type angle, value_type length) + requires(std::floating_point) + { + return Point2D(std::cos(angle) * length, std::sin(angle) * length); + } + + // Constructors ---------------------------------------------------------------------------------- + constexpr Point2D() : x(0), y(0) {} + constexpr Point2D(const Point2D& point) = default; + constexpr Point2D(Point2D&& point) noexcept = default; + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + constexpr Point2D(value_type x, value_type y) : x(x), y(y) {} + constexpr explicit Point2D(const point2d_internal::StructPoint auto& point) : + x(static_cast(point.x)), + y(static_cast(point.y)) {} + constexpr explicit Point2D(const point2d_internal::ClassPoint auto& point) : + x(static_cast(point.x())), + y(static_cast(point.y())) {} + template + constexpr explicit Point2D(const std::pair& pair) : + x(static_cast(pair.first)), + y(static_cast(pair.second)) {} + + // Destructor ------------------------------------------------------------------------------------ + constexpr ~Point2D() = default; + + // Assignment operators -------------------------------------------------------------------------- + constexpr Point2D& operator=(const Point2D& other) = default; + constexpr Point2D& operator=(Point2D&& other) noexcept = default; + + // Validators ------------------------------------------------------------------------------------ + [[nodiscard]] constexpr bool isNull() const { + if constexpr (has_epsilon_v) { + return fuzzyIsZero(x) and fuzzyIsZero(y); + } else { + return x == 0 and y == 0; + } + } + + // Arithmetic-assignment operators --------------------------------------------------------------- + inline constexpr Point2D& operator+=(const Point2D& other) { + return x += other.x, y += other.y, *this; + } + inline constexpr Point2D& operator-=(const Point2D& other) { + return x -= other.x, y -= other.y, *this; + } + inline constexpr Point2D& operator*=(value_type factor) { + return x *= factor, y *= factor, *this; + } + inline constexpr Point2D& operator/=(value_type factor) { + return x /= factor, y /= factor, *this; + } + + // Arithmetic operators -------------------------------------------------------------------------- + inline constexpr Point2D operator+(const Point2D& other) const { return Point2D(*this) += other; } + inline constexpr Point2D operator-(const Point2D& other) const { return Point2D(*this) -= other; } + inline constexpr Point2D operator*(value_type factor) const { return Point2D(*this) *= factor; } + inline constexpr Point2D operator/(value_type factor) const { return Point2D(*this) /= factor; } + + // Arithmetic friend operators ------------------------------------------------------------------- + friend inline constexpr Point2D operator*(value_type factor, const Point2D& point) { + return Point2D(factor * point.x, factor * point.y); + } + + // Sign operators -------------------------------------------------------------------------------- + inline constexpr Point2D operator+() const { return *this; } + inline constexpr Point2D operator-() const { return Point2D(-x, -y); } + + // Comparison operators -------------------------------------------------------------------------- + inline constexpr bool operator==(const Point2D& other) const { + if constexpr (has_epsilon_v) { + return fuzzyCmpEqual(x, other.x) and fuzzyCmpEqual(y, other.y); + } else { + return x == other.x and y == other.y; + } + } + + inline constexpr auto operator<=>(const Point2D& other) const { + if constexpr (has_epsilon_v) { + if (auto x_cmp = fuzzyCmpThreeWay(x, other.x); std::is_neq(x_cmp)) { + return x_cmp; + } + return fuzzyCmpThreeWay(y, other.y); + } else { + if (auto x_cmp = x <=> other.x; std::is_neq(x_cmp)) { + return x_cmp; + } + return y <=> other.y; + } + } + + // Swap ------------------------------------------------------------------------------------------ + constexpr void swap(Point2D& other) noexcept { std::swap(*this, other); } + + // Geometry -------------------------------------------------------------------------------------- + constexpr value_type dot(const Point2D& other) const { return x * other.x + y * other.y; } + constexpr value_type cross(const Point2D& other) const { return x * other.y - y * other.x; } + + constexpr value_type manhattanLength() const { return std::abs(x) + std::abs(y); } + constexpr value_type manhattanDistTo(const Point2D& other) const { + return std::abs(x - other.x) + std::abs(y - other.y); + } + + constexpr value_type lengthSquared() const { return x * x + y * y; } + constexpr std::floating_point auto length() const { return std::sqrt(lengthSquared()); } + constexpr std::floating_point auto norm() const { return std::sqrt(lengthSquared()); } + + constexpr value_type distSquaredTo(const Point2D& other) const { + return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y); + } + constexpr std::floating_point auto distTo(const Point2D& other) const { + return std::sqrt(distSquaredTo(other)); + } + + constexpr std::floating_point auto angle() const { return std::atan2(y, x); } + constexpr std::floating_point auto angleTo(const Point2D& other) const { + return std::atan2(cross(other), dot(other)); + } + + // Rotations ------------------------------------------------------------------------------------- + + constexpr void rotateCW90() & { std::swap(x, y), y = -y; } + [[nodiscard]] constexpr Point2D rotatedCW90() && { return rotateCW90(), std::move(*this); } + [[nodiscard]] constexpr Point2D rotatedCW90() const& { return Point2D(*this).rotatedCW90(); } + + constexpr void rotateCCW90() & { std::swap(x, y), x = -x; } + [[nodiscard]] constexpr Point2D rotatedCCW90() && { return rotateCCW90(), std::move(*this); } + [[nodiscard]] constexpr Point2D rotatedCCW90() const& { return Point2D(*this).rotatedCCW90(); } + + constexpr void rotateCW(value_type t) & + requires(std::floating_point) + { + value_type cos = std::cos(t); + value_type sin = std::sin(t); + + value_type rx = x * cos + y * sin; + value_type ry = -x * sin + y * cos; + + x = rx, y = ry; + } + [[nodiscard]] constexpr Point2D rotatedCW(value_type t) && + requires(std::floating_point) + { + return rotateCW(t), std::move(*this); + } + [[nodiscard]] constexpr Point2D rotatedCW(value_type t) const& + requires(std::floating_point) + { + return Point2D(*this).rotatedCW(t); + } + + constexpr void rotateCCW(value_type t) & + requires(std::floating_point) + { + value_type cos = std::cos(t); + value_type sin = std::sin(t); + + value_type rx = x * cos - y * sin; + value_type ry = x * sin + y * cos; + + x = rx, y = ry; + } + [[nodiscard]] constexpr Point2D rotatedCCW(value_type t) && + requires(std::floating_point) + { + return rotateCCW(t), std::move(*this); + } + [[nodiscard]] constexpr Point2D rotatedCCW(value_type t) const& + requires(std::floating_point) + { + return Point2D(*this).rotatedCCW(t); + } + + // Resizing and normalization -------------------------------------------------------------------- + + constexpr void resize(value_type t) & + requires(std::floating_point) + { + if constexpr (has_epsilon_v) { + if (value_type norm = this->norm(); not fuzzyIsZero(norm)) { + x *= t / norm, y *= t / norm; + } + } else { + if (value_type norm = this->norm()) { + x *= t / norm, y *= t / norm; + } + } + } + [[nodiscard]] constexpr Point2D resized(value_type t) && + requires(std::floating_point) + { + return resize(t), std::move(*this); + } + [[nodiscard]] constexpr Point2D resized(value_type t) const& + requires(std::floating_point) + { + return Point2D(*this).resized(t); + } + + constexpr void normalize() & + requires(std::floating_point) + { + if constexpr (has_epsilon_v) { + if (value_type norm = this->norm(); not fuzzyIsZero(norm)) { + x /= norm, y /= norm; + } + } else { + if (value_type norm = this->norm()) { + x /= norm, y /= norm; + } + } + } + constexpr void normalize() & + requires(std::integral) + { + if (value_type gcd = std::gcd(std::abs(x), std::abs(y))) { + x /= gcd, y /= gcd; + } + } + [[nodiscard]] constexpr Point2D normalized() && { return normalize(), std::move(*this); } + [[nodiscard]] constexpr Point2D normalized() const& { return Point2D(*this).normalized(); } + + constexpr void axesNormalize() & { + if constexpr (has_epsilon_v) { + x = fuzzyIsZero(x) ? 0 : (x > 0 ? 1 : -1), y = fuzzyIsZero(y) ? 0 : (y > 0 ? 1 : -1); + } else { + x = (x == 0) ? 0 : (x > 0 ? 1 : -1), y = (y == 0) ? 0 : (y > 0 ? 1 : -1); + } + } + [[nodiscard]] constexpr Point2D axesNormalized() && { return axesNormalize(), std::move(*this); } + [[nodiscard]] constexpr Point2D axesNormalized() const& { + return Point2D(*this).axesNormalized(); + } + + constexpr void transpose() & { std::swap(x, y); } + [[nodiscard]] constexpr Point2D transposed() && { return transpose(), std::move(*this); } + [[nodiscard]] constexpr Point2D transposed() const& { return Point2D(*this).transposed(); } + + // Array-like ------------------------------------------------------------------------------------ + [[nodiscard]] static consteval size_type size() { return 2; } + + inline constexpr reference operator[](size_type pos) { + switch (pos) { + case 0: return x; + case 1: return y; + default: throw std::out_of_range("Point2D operator[]: index out of range."); + } + } + inline constexpr value_type operator[](size_type pos) const { + switch (pos) { + case 0: return x; + case 1: return y; + default: throw std::out_of_range("Point2D operator[]: index out of range."); + } + } + + // Iterators ------------------------------------------------------------------------------------- + constexpr iterator begin() noexcept { return iterator{this, /*index=*/0}; } + [[nodiscard]] constexpr const_iterator begin() const noexcept { + return const_iterator{this, /*index=*/0}; + } + constexpr iterator end() noexcept { return iterator{this, size()}; } + [[nodiscard]] constexpr const_iterator end() const noexcept { + return const_iterator{this, size()}; + } + + constexpr reverse_iterator rbegin() noexcept { return reverse_iterator{end()}; } + [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { + return const_reverse_iterator{end()}; + } + constexpr reverse_iterator rend() noexcept { return reverse_iterator{begin()}; } + [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { + return const_reverse_iterator{begin()}; + } + + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { + return const_iterator{this, /*index=*/0}; + } + [[nodiscard]] constexpr const_iterator cend() const noexcept { + return const_iterator{this, size()}; + } + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { + return const_reverse_iterator{end()}; + } + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { + return const_reverse_iterator{begin()}; + } + + // Input/Output operators ------------------------------------------------------------------------ + friend inline std::istream& operator>>(std::istream& is, Point2D& point) { + return is >> point.x >> point.y; + } + + friend inline std::ostream& operator<<(std::ostream& os, const Point2D& point) { + return os << "(x = " << point.x << ", y = " << point.y << ")"; + } +}; + +// Deduction guides -------------------------------------------------------------------------------- +Point2D() -> Point2D; + +template +Point2D(T, U) -> Point2D>; + +} // namespace robocin + +#endif // ROBOCIN_GEOMETRY_POINT2D_H diff --git a/robocin/geometry/point2d_test.cpp b/robocin/geometry/point2d_test.cpp new file mode 100644 index 0000000..db7d7ef --- /dev/null +++ b/robocin/geometry/point2d_test.cpp @@ -0,0 +1,752 @@ +// +// Created by José Cruz on 24/02/23. +// Copyright (c) 2023 RobôCIn. +// + +#include "robocin/geometry/point2d.h" +#include "robocin/utility/internal/test/epsilon_injector.h" + +#include + +namespace robocin { +namespace { + +using ::testing::Test; +using ::testing::Types; + +template +class Point2DTest : public Test {}; + +using TestTypes = Types; +TYPED_TEST_SUITE(Point2DTest, TestTypes); + +// Static constructors ----------------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, Origin) { + const Point2D kPt = Point2D::origin(); + + EXPECT_EQ(kPt.x, 0); + EXPECT_EQ(kPt.y, 0); +} + +TYPED_TEST(Point2DTest, FromPolarGivenAngle) { + if constexpr (std::is_floating_point_v) { + static constexpr TypeParam kEpsilon = epsilon_v; + static constexpr TypeParam kPi = std::numbers::pi_v; + static constexpr TypeParam kSqrt2 = std::numbers::sqrt2_v; + + struct InOut { + TypeParam angle; + Point2D expected; + }; + + static constexpr std::array kOctants = { + InOut{0.0, {/*x=*/1.0, /*y=*/0.0}}, // 0º + InOut{kPi / 4.0, {/*x=*/kSqrt2 / 2.0, /*y=*/kSqrt2 / 2.0}}, // 45º + InOut{kPi / 2.0, {/*x=*/0.0, /*y=*/1.0}}, // 90º + InOut{3.0 * kPi / 4.0, {/*x=*/-kSqrt2 / 2.0, /*y=*/kSqrt2 / 2.0}}, // 135º + InOut{kPi, {/*x=*/-1.0, /*y=*/0.0}}, // 180º + InOut{5.0 * kPi / 4.0, {/*x=*/-kSqrt2 / 2.0, /*y=*/-kSqrt2 / 2.0}}, // 225º + InOut{3.0 * kPi / 2.0, {/*x=*/0.0, /*y=*/-1.0}}, // 270º + InOut{7.0 * kPi / 4.0, {/*x=*/kSqrt2 / 2.0, /*y=*/-kSqrt2 / 2.0}}, // 315º + }; + + for (auto [angle, expected] : kOctants) { + const Point2D kPt = Point2D::fromPolar(angle); + + EXPECT_NEAR(kPt.x, expected.x, kEpsilon); + EXPECT_NEAR(kPt.y, expected.y, kEpsilon); + } + } +} + +TYPED_TEST(Point2DTest, FromPolarGivenAngleAndLength) { + if constexpr (std::is_floating_point_v) { + static constexpr TypeParam kEpsilon = epsilon_v; + static constexpr TypeParam kPi = std::numbers::pi_v; + static constexpr TypeParam kSqrt2 = std::numbers::sqrt2_v; + + struct InOut { + TypeParam angle; + TypeParam length; + Point2D expected; + }; + + static constexpr std::array kOctants = { + InOut{0.0, 1.0, {/*x=*/1.0, /*y=*/0.0}}, // 0º + InOut{kPi / 4.0, 2.0, {/*x=*/kSqrt2 / 2.0, /*y=*/kSqrt2 / 2.0}}, // 45º + InOut{kPi / 2.0, 4.0, {/*x=*/0.0, /*y=*/1.0}}, // 90º + InOut{3.0 * kPi / 4.0, 8.0, {/*x=*/-kSqrt2 / 2.0, /*y=*/kSqrt2 / 2.0}}, // 135º + InOut{kPi, 16.0, {/*x=*/-1.0, /*y=*/0.0}}, // 180º + InOut{5.0 * kPi / 4.0, 32.0, {/*x=*/-kSqrt2 / 2.0, /*y=*/-kSqrt2 / 2.0}}, // 225º + InOut{3.0 * kPi / 2.0, 64.0, {/*x=*/0.0, /*y=*/-1.0}}, // 270º + InOut{7.0 * kPi / 4.0, 128.0, {/*x=*/kSqrt2 / 2.0, /*y=*/-kSqrt2 / 2.0}}, // 315º + }; + + for (auto [angle, length, expected] : kOctants) { + const Point2D kPt = Point2D::fromPolar(angle, length); + + EXPECT_NEAR(kPt.x, length * expected.x, kEpsilon); + EXPECT_NEAR(kPt.y, length * expected.y, kEpsilon); + } + } +} + +// Constructors ------------------------------------------------------------------------------------ + +TYPED_TEST(Point2DTest, DefaultConstructor) { + const Point2D kPt; + + EXPECT_EQ(kPt.x, 0); + EXPECT_EQ(kPt.y, 0); +} + +TYPED_TEST(Point2DTest, CopyConstructor) { + const Point2D kOther(/*x=*/1, /*y=*/2); + const Point2D kPt(kOther); + + EXPECT_EQ(kPt.x, kOther.x); + EXPECT_EQ(kPt.y, kOther.y); +} + +TYPED_TEST(Point2DTest, MoveConstructor) { + Point2D other(/*x=*/1, /*y=*/2); + const Point2D kPt(std::move(other)); + + EXPECT_EQ(kPt.x, 1); + EXPECT_EQ(kPt.y, 2); +} + +TYPED_TEST(Point2DTest, ConstructorGivenXAndY) { + const Point2D kPt(/*x=*/1, /*y=*/2); + + EXPECT_EQ(kPt.x, 1); + EXPECT_EQ(kPt.y, 2); +} + +TYPED_TEST(Point2DTest, ConstructorGivenStructPoint) { + struct OtherPoint2D { + TypeParam x; + TypeParam y; + }; + + const OtherPoint2D kOther{.x = 1, .y = 2}; + const Point2D kPt(kOther); + + EXPECT_EQ(kPt.x, kOther.x); + EXPECT_EQ(kPt.y, kOther.y); +} + +TYPED_TEST(Point2DTest, ConstructorGivenClassPoint) { + class OtherPoint2D { + public: + OtherPoint2D(TypeParam x, TypeParam y) : // NOLINT(bugprone-easily-swappable-parameters) + x_(x), + y_(y) {} + + [[nodiscard]] TypeParam x() const { return x_; } + [[nodiscard]] TypeParam y() const { return y_; } + + private: + TypeParam x_; + TypeParam y_; + }; + + const OtherPoint2D kOther{/*x=*/1, /*y=*/2}; + const Point2D kPt(kOther); + + EXPECT_EQ(kPt.x, kOther.x()); + EXPECT_EQ(kPt.y, kOther.y()); +} + +TYPED_TEST(Point2DTest, ConstructorGivenPair) { + const std::pair kPair{1, 2}; + const Point2D kPt(kPair); + + EXPECT_EQ(kPt.x, kPair.first); + EXPECT_EQ(kPt.y, kPair.second); +} + +// Validators -------------------------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, IsNull) { + const Point2D kPt(/*x=*/0, /*y=*/0); + EXPECT_TRUE(kPt.isNull()); + + const Point2D kPt2(/*x=*/1, /*y=*/2); + EXPECT_FALSE(kPt2.isNull()); +} + +// Arithmetic assignment operators ----------------------------------------------------------------- + +TYPED_TEST(Point2DTest, AdditionAssignment) { + Point2D pt(/*x=*/1, /*y=*/2); + pt += Point2D(/*x=*/3, /*y=*/4); + + EXPECT_EQ(pt.x, 4); + EXPECT_EQ(pt.y, 6); +} + +TYPED_TEST(Point2DTest, SubtractionAssignment) { + Point2D pt(/*x=*/1, /*y=*/2); + pt -= Point2D(/*x=*/3, /*y=*/4); + + EXPECT_EQ(pt.x, -2); + EXPECT_EQ(pt.y, -2); +} + +TYPED_TEST(Point2DTest, MultiplicationAssignment) { + Point2D pt(/*x=*/1, /*y=*/2); + pt *= 3; + + EXPECT_EQ(pt.x, 3); + EXPECT_EQ(pt.y, 6); +} + +TYPED_TEST(Point2DTest, DivisionAssignment) { + Point2D pt(/*x=*/1, /*y=*/2); + pt /= 2; + + EXPECT_EQ(pt.x, static_cast(1) / 2); + EXPECT_EQ(pt.y, static_cast(2) / 2); +} + +// Arithmetic operators ---------------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, Addition) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2(/*x=*/3, /*y=*/4); + + const Point2D kPt3 = kPt1 + kPt2; + + EXPECT_EQ(kPt3.x, 4); + EXPECT_EQ(kPt3.y, 6); +} + +TYPED_TEST(Point2DTest, Subtraction) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2(/*x=*/3, /*y=*/4); + + const Point2D kPt3 = kPt2 - kPt1; + + EXPECT_EQ(kPt3.x, 2); + EXPECT_EQ(kPt3.y, 2); +} + +TYPED_TEST(Point2DTest, Multiplication) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1 * 3; + + EXPECT_EQ(kPt2.x, 3); + EXPECT_EQ(kPt2.y, 6); + + const Point2D kPt3 = 3 * kPt1; + + EXPECT_EQ(kPt3.x, 3); + EXPECT_EQ(kPt3.y, 6); +} + +TYPED_TEST(Point2DTest, Division) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1 / 2; + + EXPECT_EQ(kPt2.x, static_cast(1) / 2); + EXPECT_EQ(kPt2.y, static_cast(2) / 2); +} + +// Sign operators ---------------------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, UnaryPlus) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = +kPt1; + + EXPECT_EQ(kPt2.x, 1); + EXPECT_EQ(kPt2.y, 2); +} + +TYPED_TEST(Point2DTest, UnaryMinus) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = -kPt1; + + EXPECT_EQ(kPt2.x, -1); + EXPECT_EQ(kPt2.y, -2); +} + +// Equality operators ------------------------------------------------------------------------------ + +TYPED_TEST(Point2DTest, Equality) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2(/*x=*/1, /*y=*/2); + + EXPECT_TRUE(kPt1 == kPt2); + + const Point2D kPt3(/*x=*/3, /*y=*/4); + EXPECT_FALSE(kPt1 == kPt3); +} + +// Three-way comparison operator ------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, ThreeWayComparison) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2(/*x=*/1, /*y=*/2); + + EXPECT_EQ(kPt1 <=> kPt2, std::strong_ordering::equal); + + const Point2D kPt3(/*x=*/3, /*y=*/4); + EXPECT_EQ(kPt1 <=> kPt3, std::strong_ordering::less); + EXPECT_EQ(kPt3 <=> kPt1, std::strong_ordering::greater); +} + +// Swap -------------------------------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, Swapping) { + Point2D pt1(/*x=*/1, /*y=*/2); + Point2D pt2(/*x=*/3, /*y=*/4); + + pt1.swap(pt2); + + EXPECT_EQ(pt1.x, 3); + EXPECT_EQ(pt1.y, 4); + EXPECT_EQ(pt2.x, 1); + EXPECT_EQ(pt2.y, 2); +} + +// Geometry ---------------------------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, DotProduct) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2(/*x=*/3, /*y=*/4); + + EXPECT_EQ(kPt1.dot(kPt2), 11); +} + +TYPED_TEST(Point2DTest, CrossProduct) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2(/*x=*/3, /*y=*/4); + + EXPECT_EQ(kPt1.cross(kPt2), -2); +} + +TYPED_TEST(Point2DTest, ManhattanLength) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + + EXPECT_EQ(kPt1.manhattanLength(), 3); +} + +TYPED_TEST(Point2DTest, ManhattanDistTo) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2(/*x=*/3, /*y=*/4); + + EXPECT_EQ(kPt1.manhattanDistTo(kPt2), 4); +} + +TYPED_TEST(Point2DTest, LengthSquared) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + + EXPECT_EQ(kPt1.lengthSquared(), 5); +} + +TYPED_TEST(Point2DTest, Length) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + + using point2d_t = Point2D; + using length_result_t = std::invoke_result_t; + + static constexpr std::floating_point auto kEpsilon = epsilon_v; + + EXPECT_NEAR(kPt1.length(), std::sqrt(5), kEpsilon); +} + +TYPED_TEST(Point2DTest, Norm) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + + using point2d_t = Point2D; + using norm_result_t = std::invoke_result_t; + + static constexpr std::floating_point auto kEpsilon = epsilon_v; + + EXPECT_NEAR(kPt1.norm(), std::sqrt(5), kEpsilon); +} + +TYPED_TEST(Point2DTest, DistSquaredTo) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2(/*x=*/3, /*y=*/4); + + EXPECT_EQ(kPt1.distSquaredTo(kPt2), 8); +} + +TYPED_TEST(Point2DTest, DistTo) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2(/*x=*/3, /*y=*/4); + + using point2d_t = Point2D; + using dist_to_result_t = std::invoke_result_t; + + static constexpr std::floating_point auto kEpsilon = epsilon_v; + + EXPECT_NEAR(kPt1.distTo(kPt2), std::sqrt(8), kEpsilon); +} + +TYPED_TEST(Point2DTest, Angle) { + const Point2D kPt1(/*x=*/0, /*y=*/1); + + using point2d_t = Point2D; + using angle_result_t = std::invoke_result_t; + + static constexpr angle_result_t kEpsilon = epsilon_v; + static constexpr std::floating_point auto kPi = std::numbers::pi_v; + + EXPECT_NEAR(kPt1.angle(), kPi / 2, kEpsilon); +} + +TYPED_TEST(Point2DTest, AngleTo) { + const Point2D kPt1(/*x=*/0, /*y=*/1); + const Point2D kPt2(/*x=*/1, /*y=*/0); + + using point2d_t = Point2D; + using angle_to_result_t = std::invoke_result_t; + + static constexpr angle_to_result_t kEpsilon = epsilon_v; + static constexpr std::floating_point auto kPi = std::numbers::pi_v; + + EXPECT_NEAR(kPt1.angleTo(kPt2), -kPi / 2, kEpsilon); +} + +// Rotations --------------------------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, RotateCW90) { + Point2D pt1(/*x=*/1, /*y=*/2); + pt1.rotateCW90(); + + EXPECT_EQ(pt1.x, 2); + EXPECT_EQ(pt1.y, -1); +} + +TYPED_TEST(Point2DTest, RotatedCW90) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1.rotatedCW90(); + + EXPECT_EQ(kPt2.x, 2); + EXPECT_EQ(kPt2.y, -1); +} + +TYPED_TEST(Point2DTest, RotateCCW90) { + Point2D pt1(/*x=*/1, /*y=*/2); + pt1.rotateCCW90(); + + EXPECT_EQ(pt1.x, -2); + EXPECT_EQ(pt1.y, 1); +} + +TYPED_TEST(Point2DTest, RotatedCCW90) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1.rotatedCCW90(); + + EXPECT_EQ(kPt2.x, -2); + EXPECT_EQ(kPt2.y, 1); +} + +TYPED_TEST(Point2DTest, RotateCW) { + if constexpr (std::floating_point) { + static constexpr std::floating_point auto kEpsilon = epsilon_v; + static constexpr std::floating_point auto kPi = std::numbers::pi_v; + + Point2D pt1(/*x=*/1, /*y=*/2); + pt1.rotateCW(kPi / 2); + + EXPECT_NEAR(pt1.x, 2, kEpsilon); + EXPECT_NEAR(pt1.y, -1, kEpsilon); + } +} + +TYPED_TEST(Point2DTest, RotatedCW) { + if constexpr (std::floating_point) { + static constexpr std::floating_point auto kEpsilon = epsilon_v; + static constexpr std::floating_point auto kPi = std::numbers::pi_v; + + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1.rotatedCW(kPi / 2); + + EXPECT_NEAR(kPt2.x, 2, kEpsilon); + EXPECT_NEAR(kPt2.y, -1, kEpsilon); + } +} + +TYPED_TEST(Point2DTest, RotateCCW) { + if constexpr (std::floating_point) { + static constexpr std::floating_point auto kEpsilon = epsilon_v; + static constexpr std::floating_point auto kPi = std::numbers::pi_v; + + Point2D pt1(/*x=*/1, /*y=*/2); + pt1.rotateCCW(kPi / 2); + + EXPECT_NEAR(pt1.x, -2, kEpsilon); + EXPECT_NEAR(pt1.y, 1, kEpsilon); + } +} + +TYPED_TEST(Point2DTest, RotatedCCW) { + if constexpr (std::floating_point) { + static constexpr std::floating_point auto kEpsilon = epsilon_v; + static constexpr std::floating_point auto kPi = std::numbers::pi_v; + + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1.rotatedCCW(kPi / 2); + + EXPECT_NEAR(kPt2.x, -2, kEpsilon); + EXPECT_NEAR(kPt2.y, 1, kEpsilon); + } +} + +// Resizing and Normalization ---------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, Resize) { + if constexpr (std::floating_point) { + static constexpr TypeParam kResizeFactor = 2; + static constexpr std::floating_point auto kEpsilon = epsilon_v; + + Point2D pt1(/*x=*/1, /*y=*/2); + pt1.resize(kResizeFactor); + + EXPECT_NEAR(pt1.x, 1.0 * kResizeFactor / std::sqrt(5), kEpsilon); + EXPECT_NEAR(pt1.y, 2.0 * kResizeFactor / std::sqrt(5), kEpsilon); + } +} + +TYPED_TEST(Point2DTest, Resized) { + if constexpr (std::floating_point) { + static constexpr TypeParam kResizeFactor = 2; + static constexpr std::floating_point auto kEpsilon = epsilon_v; + + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1.resized(kResizeFactor); + + EXPECT_NEAR(kPt2.x, 1.0 * kResizeFactor / std::sqrt(5), kEpsilon); + EXPECT_NEAR(kPt2.y, 2.0 * kResizeFactor / std::sqrt(5), kEpsilon); + } +} + +TYPED_TEST(Point2DTest, NormalizeFloatingPoint) { + if constexpr (std::floating_point) { + static constexpr std::floating_point auto kEpsilon = epsilon_v; + + Point2D pt1(/*x=*/1, /*y=*/2); + pt1.normalize(); + + EXPECT_NEAR(pt1.x, 1.0 / std::sqrt(5), kEpsilon); + EXPECT_NEAR(pt1.y, 2.0 / std::sqrt(5), kEpsilon); + } +} + +TYPED_TEST(Point2DTest, NormalizeIntegral) { + if constexpr (std::integral) { + Point2D pt1(/*x=*/1, /*y=*/2); + pt1.normalize(); + + EXPECT_EQ(pt1.x, 1); + EXPECT_EQ(pt1.y, 2); + } +} + +TYPED_TEST(Point2DTest, NormalizedFloatingPoint) { + if constexpr (std::floating_point) { + static constexpr std::floating_point auto kEpsilon = epsilon_v; + + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1.normalized(); + + EXPECT_NEAR(kPt2.x, 1.0 / std::sqrt(5), kEpsilon); + EXPECT_NEAR(kPt2.y, 2.0 / std::sqrt(5), kEpsilon); + } +} + +TYPED_TEST(Point2DTest, NormalizedIntegral) { + if constexpr (std::integral) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1.normalized(); + + EXPECT_EQ(kPt2.x, 1); + EXPECT_EQ(kPt2.y, 2); + } +} + +TYPED_TEST(Point2DTest, AxesNormalize) { + Point2D pt1(/*x=*/1, /*y=*/2); + pt1.axesNormalize(); + + EXPECT_EQ(pt1.x, 1); + EXPECT_EQ(pt1.y, 1); +} + +TYPED_TEST(Point2DTest, AxesNormalized) { + const Point2D kPt1(/*x=*/1, /*y=*/2); + const Point2D kPt2 = kPt1.axesNormalized(); + + EXPECT_EQ(kPt2.x, 1); + EXPECT_EQ(kPt2.y, 1); +} + +// Array-like -------------------------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, Size) { + static_assert(Point2D::size() == 2); + + const Point2D kPt; + EXPECT_EQ(kPt.size(), 2); +} + +TYPED_TEST(Point2DTest, DimensionalAccess) { + Point2D pt(/*x=*/1, /*y=*/2); + + EXPECT_EQ(pt[0], 1); + EXPECT_EQ(pt[1], 2); + + EXPECT_EQ(&(pt[0]), &(pt.x)); + EXPECT_EQ(&(pt[1]), &(pt.y)); + + EXPECT_THROW(pt[2], std::out_of_range); + + const Point2D kPt(/*x=*/3, /*y=*/4); + + EXPECT_EQ(kPt[0], 3); + EXPECT_EQ(kPt[1], 4); + + EXPECT_THROW(kPt[2], std::out_of_range); + + static_assert(!std::is_reference_v); // const point returns a copy. + static_assert(!std::is_reference_v); // const point returns a copy. +} + +// Iterators --------------------------------------------------------------------------------------- + +TYPED_TEST(Point2DTest, Iterator) { + EXPECT_TRUE(std::random_access_iterator::iterator>); + EXPECT_TRUE(std::random_access_iterator::const_iterator>); + EXPECT_TRUE(std::random_access_iterator::reverse_iterator>); + EXPECT_TRUE(std::random_access_iterator::const_reverse_iterator>); +} + +TYPED_TEST(Point2DTest, Begin) { + Point2D pt(/*x=*/1, /*y=*/2); + EXPECT_EQ(&(*pt.begin()), &(pt.x)); + + const Point2D kPt(/*x=*/3, /*y=*/4); + EXPECT_EQ(&(*kPt.begin()), &(kPt.x)); + + static_assert(std::is_same_v::const_iterator>); +} + +TYPED_TEST(Point2DTest, End) { + Point2D pt(/*x=*/1, /*y=*/2); + EXPECT_THROW(*pt.end(), std::out_of_range); + + const Point2D kPt(/*x=*/3, /*y=*/4); + EXPECT_THROW(*kPt.end(), std::out_of_range); + + static_assert(std::is_same_v::const_iterator>); +} + +TYPED_TEST(Point2DTest, Cbegin) { + Point2D pt(/*x=*/1, /*y=*/2); // NOLINT(misc-const-correctness) + EXPECT_EQ(&(*pt.cbegin()), &(pt.x)); + + const Point2D kPt(/*x=*/3, /*y=*/4); + EXPECT_EQ(&(*kPt.cbegin()), &(kPt.x)); +} + +TYPED_TEST(Point2DTest, Cend) { + Point2D pt(/*x=*/1, /*y=*/2); // NOLINT(misc-const-correctness) + EXPECT_THROW(*pt.cend(), std::out_of_range); + + const Point2D kPt(/*x=*/3, /*y=*/4); + EXPECT_THROW(*kPt.cend(), std::out_of_range); +} + +TYPED_TEST(Point2DTest, Rbegin) { + Point2D pt(/*x=*/1, /*y=*/2); + EXPECT_EQ(&(*pt.rbegin()), &(pt.y)); + + const Point2D kPt(/*x=*/3, /*y=*/4); + EXPECT_EQ(&(*kPt.rbegin()), &(kPt.y)); + + static_assert( + std::is_same_v::const_reverse_iterator>); +} + +TYPED_TEST(Point2DTest, Rend) { + Point2D pt(/*x=*/1, /*y=*/2); + EXPECT_THROW(*pt.rend(), std::out_of_range); + + const Point2D kPt(/*x=*/3, /*y=*/4); + EXPECT_THROW(*kPt.rend(), std::out_of_range); + + static_assert( + std::is_same_v::const_reverse_iterator>); +} + +TYPED_TEST(Point2DTest, Crbegin) { + Point2D pt(/*x=*/1, /*y=*/2); // NOLINT(misc-const-correctness) + EXPECT_EQ(&(*pt.crbegin()), &(pt.y)); + + const Point2D kPt(/*x=*/3, /*y=*/4); + EXPECT_EQ(&(*kPt.crbegin()), &(kPt.y)); +} + +TYPED_TEST(Point2DTest, Crend) { + Point2D pt(/*x=*/1, /*y=*/2); // NOLINT(misc-const-correctness) + EXPECT_THROW(*pt.crend(), std::out_of_range); + + const Point2D kPt(/*x=*/3, /*y=*/4); + EXPECT_THROW(*kPt.crend(), std::out_of_range); +} + +// Input/Output ------------------------------------------------------------------------------------ + +TYPED_TEST(Point2DTest, Input) { + Point2D pt; + std::istringstream iss("1 2"); + iss >> pt; + + EXPECT_EQ(pt.x, 1); + EXPECT_EQ(pt.y, 2); +} + +TYPED_TEST(Point2DTest, Output) { + const Point2D kPt(/*x=*/1, /*y=*/2); + std::ostringstream oss; + oss << kPt; + + EXPECT_EQ(oss.str(), "(x = 1, y = 2)"); +} + +// Deduction Guides -------------------------------------------------------------------------------- + +TEST(Point2DTest, EmptyDeductionGuide) { + const Point2D kPt; + + EXPECT_EQ(kPt.x, 0); + EXPECT_EQ(kPt.y, 0); + + static_assert(std::is_same_v); +} + +TEST(Point2DTest, ValueDeductionGuide) { + static constexpr int kFirstCoord = 1; + static constexpr double kSecondCoord = 2.0; + + const Point2D kPt(kFirstCoord, kSecondCoord); + + using common_type_t = std::common_type_t; + + EXPECT_EQ(kPt.x, static_cast(kFirstCoord)); + EXPECT_EQ(kPt.y, static_cast(kSecondCoord)); + + static_assert(std::is_same_v); +} + +} // namespace +} // namespace robocin