From 4b91547a64fcf725b00f6e5cecbaebcfe125e50a Mon Sep 17 00:00:00 2001 From: mhco0 Date: Sun, 29 Oct 2023 20:22:05 +0000 Subject: [PATCH] Adding Rect2d --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 152 ++-- .vscode/settings.json | 6 + README.md | 99 +-- robocin/CMakeLists.txt | 3 +- robocin/geometry/CMakeLists.txt | 27 + robocin/geometry/README.md | 203 +++++ robocin/geometry/internal/point2d_internal.h | 109 +++ robocin/geometry/internal/rect2d_internal.h | 111 +++ robocin/geometry/point2d.cpp | 17 + robocin/geometry/point2d.h | 352 +++++++++ robocin/geometry/point2d_test.cpp | 752 +++++++++++++++++++ robocin/geometry/rect2d.cpp | 17 + robocin/geometry/rect2d.h | 227 ++++++ robocin/geometry/rect2d_test.cpp | 189 +++++ 15 files changed, 2139 insertions(+), 127 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 robocin/geometry/CMakeLists.txt create mode 100644 robocin/geometry/README.md create mode 100644 robocin/geometry/internal/point2d_internal.h create mode 100644 robocin/geometry/internal/rect2d_internal.h create mode 100644 robocin/geometry/point2d.cpp create mode 100644 robocin/geometry/point2d.h create mode 100644 robocin/geometry/point2d_test.cpp create mode 100644 robocin/geometry/rect2d.cpp create mode 100644 robocin/geometry/rect2d.h create mode 100644 robocin/geometry/rect2d_test.cpp diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b484e63..97247d6 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1 +1 @@ -FROM robocin/cpp-ubuntu-latest:latest +FROM robocin/cpp-ubuntu-latest:latest diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4b93d41..d1f349f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,76 +1,76 @@ -// For format details, see https://aka.ms/devcontainer.json. -{ - "name": "C++", - "build": { - "dockerfile": "Dockerfile" - }, - "runArgs": [ - "-e", - "DISPLAY=${env:DISPLAY}", - "--network=host" - ], - "customizations": { - "vscode": { - "extensions": [ - // C/C++ Extension Pack - "ms-vscode.cpptools-extension-pack", - // clangd - "llvm-vs-code-extensions.vscode-clangd", - // GitLens - Git supercharged - "eamodio.gitlens", - // GitHub Copilot - "GitHub.copilot", - // Code Spell Checker - "streetsidesoftware.code-spell-checker", - // YAML - "redhat.vscode-yaml", - // Protobuf Language Support - "zxh404.vscode-proto3", - // Python - "ms-python.python" - ], - "settings": { - // C++ Settings - "[cpp]": { - "editor.insertSpaces": true, - "editor.tabSize": 2, - "editor.formatOnSave": true, - "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" - }, - // C/C++ Settings - "C_Cpp.intelliSenseEngine": "disabled", // disabling IntelliSense Engine to use clangd - // clangd Settings - "clangd.path": "/usr/bin/clangd", - // CMake Settings - "cmake.buildDirectory": "${workspaceFolder}/build", - "cmake.cmakePath": "/usr/bin/cmake", - "cmake.generator": "Ninja", - // Code Spell Checker Settings - "cSpell.language": "en, pt-BR", - "cSpell.words": [ - "robocin", - "RoboCIn", - "RobôCIn" - ], - // Editor Settings - "editor.detectIndentation": true, - // Files Settings - "files.insertFinalNewline": true, - // JSON Settings - "[json]": { - "editor.insertSpaces": true, - "editor.tabSize": 2, - "editor.formatOnSave": true, - "editor.defaultFormatter": "vscode.json-language-features" - }, - // YAML Settings - "[yaml]": { - "editor.insertSpaces": true, - "editor.tabSize": 2, - "editor.formatOnSave": true, - "editor.defaultFormatter": "redhat.vscode-yaml" - } - } - } - } -} +// For format details, see https://aka.ms/devcontainer.json. +{ + "name": "C++", + "build": { + "dockerfile": "Dockerfile" + }, + "runArgs": [ + "-e", + "DISPLAY=${env:DISPLAY}", + "--network=host" + ], + "customizations": { + "vscode": { + "extensions": [ + // C/C++ Extension Pack + "ms-vscode.cpptools-extension-pack", + // clangd + "llvm-vs-code-extensions.vscode-clangd", + // GitLens - Git supercharged + "eamodio.gitlens", + // GitHub Copilot + "GitHub.copilot", + // Code Spell Checker + "streetsidesoftware.code-spell-checker", + // YAML + "redhat.vscode-yaml", + // Protobuf Language Support + "zxh404.vscode-proto3", + // Python + "ms-python.python" + ], + "settings": { + // C++ Settings + "[cpp]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.formatOnSave": true, + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" + }, + // C/C++ Settings + "C_Cpp.intelliSenseEngine": "disabled", // disabling IntelliSense Engine to use clangd + // clangd Settings + "clangd.path": "/usr/bin/clangd", + // CMake Settings + "cmake.buildDirectory": "${workspaceFolder}/build", + "cmake.cmakePath": "/usr/bin/cmake", + "cmake.generator": "Ninja", + // Code Spell Checker Settings + "cSpell.language": "en, pt-BR", + "cSpell.words": [ + "robocin", + "RoboCIn", + "RobôCIn" + ], + // Editor Settings + "editor.detectIndentation": true, + // Files Settings + "files.insertFinalNewline": true, + // JSON Settings + "[json]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.formatOnSave": true, + "editor.defaultFormatter": "vscode.json-language-features" + }, + // YAML Settings + "[yaml]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.formatOnSave": true, + "editor.defaultFormatter": "redhat.vscode-yaml" + } + } + } + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8849ba9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "xtr1common": "cpp", + "limits": "cpp" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 9af3fe9..607274f 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,50 @@ -# [wip] robocin-cpp - -The repository provides the core library for our team's robotics applications. - -## Table of Contents - -- [About](#about) -- [Quickstart](#quickstart) -- [Building](#build) -- [Codemap](#codemap) -- [Contributing](#contributing) -- [License](#license) - - - -## About - -In progress... - - - -## Quickstart - -In progress... - - - -## Building - -[CMake](https://cmake.org/) is the official build system of the `robocin-cpp` project. - - - -## Codemap - -- [`utility`](robocin/utility): a collection of utility and helper code. - - - -## Contributing - -We welcome contributions to the `robocin-cpp` library. If you would like to contribute, please feel free to open issues -or send an email to [robocin@cin.ufpe.br](mailto:robocin@cin.ufpe.br). - - - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. +# [wip] robocin-cpp + +The repository provides the core library for our team's robotics applications. + +## Table of Contents + +- [About](#about) +- [Quickstart](#quickstart) +- [Building](#build) +- [Codemap](#codemap) +- [Contributing](#contributing) +- [License](#license) + + + +## About + +In progress... + + + +## Quickstart + +In progress... + + + +## Building + +[CMake](https://cmake.org/) is the official build system of the `robocin-cpp` project. + + + +## Codemap + +- [`geometry`](robocin/geometry): a collection of geometric classes; +- [`utility`](robocin/utility): a collection of utility and helper code; + + + +## Contributing + +We welcome contributions to the `robocin-cpp` library. If you would like to contribute, please feel free to open issues +or send an email to [robocin@cin.ufpe.br](mailto:robocin@cin.ufpe.br). + + + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. diff --git a/robocin/CMakeLists.txt b/robocin/CMakeLists.txt index f8474db..f86e101 100644 --- a/robocin/CMakeLists.txt +++ b/robocin/CMakeLists.txt @@ -1 +1,2 @@ -add_subdirectory(utility) +add_subdirectory(utility) +add_subdirectory(geometry) diff --git a/robocin/geometry/CMakeLists.txt b/robocin/geometry/CMakeLists.txt new file mode 100644 index 0000000..ada7c0d --- /dev/null +++ b/robocin/geometry/CMakeLists.txt @@ -0,0 +1,27 @@ +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 +) + + +robocin_cpp_library( + NAME rect2d + HDRS rect2d.h + SRCS rect2d.cpp +) + + +robocin_cpp_test( + NAME rect2d_test + HDRS ../utility/internal/test/epsilon_injector.h + SRCS rect2d_test.cpp + DEPS rect2d +) diff --git a/robocin/geometry/README.md b/robocin/geometry/README.md new file mode 100644 index 0000000..b25985a --- /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..167b305 --- /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/internal/rect2d_internal.h b/robocin/geometry/internal/rect2d_internal.h new file mode 100644 index 0000000..8504557 --- /dev/null +++ b/robocin/geometry/internal/rect2d_internal.h @@ -0,0 +1,111 @@ +// +// Created by Marcos Oliveira on 10/14/23. +// Copyright (c) 2023 RobôCIn. +// + +#pragma once + +#include +#include + +namespace robocin::rect2d_internal { +template +concept StructRect = requires(RT rect) { + rect.top_left; + rect.width; + rect.height; + }; + +template +concept ClassRect = requires(RT rect) { + rect.TopLeft(); + rect.Width(); + rect.Height(); + }; + +template +class iterator { + using rect_type = RT; + using rect_pointer = rect_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(rect_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_->top_left.x; + case 1: return ptr_->top_left.y; + case 2: return ptr_->width; + case 3: return ptr_->height; + default: throw std::out_of_range("Rect2D::iterator operator*: index out of range."); + } + } + + constexpr reference operator[](difference_type n) const { + switch (index_ + n) { + case 0: return ptr_->top_left.x; + case 1: return ptr_->top_left.y; + case 2: return ptr_->width; + case 3: return ptr_->height; + default: throw std::out_of_range("Rect2D::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: + rect_pointer ptr_{nullptr}; + difference_type index_{0}; +}; + +} // namespace robocin::rect2d_internal diff --git a/robocin/geometry/point2d.cpp b/robocin/geometry/point2d.cpp new file mode 100644 index 0000000..e6749ab --- /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..9804a72 --- /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..dbf35d4 --- /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 diff --git a/robocin/geometry/rect2d.cpp b/robocin/geometry/rect2d.cpp new file mode 100644 index 0000000..cbb1aa2 --- /dev/null +++ b/robocin/geometry/rect2d.cpp @@ -0,0 +1,17 @@ +// +// Created by Marcos Oliveira on 10/14/2023. +// Copyright (c) 2023 RobôCIn. +// + +#include "robocin/geometry/rect2d.h" + +namespace robocin { + +template struct Rect2D; +template struct Rect2D; +template struct Rect2D; +template struct Rect2D; +template struct Rect2D; +template struct Rect2D; + +} // namespace robocin diff --git a/robocin/geometry/rect2d.h b/robocin/geometry/rect2d.h new file mode 100644 index 0000000..e7822a5 --- /dev/null +++ b/robocin/geometry/rect2d.h @@ -0,0 +1,227 @@ +// +// Created by Marcos Oliveira on 10/14/23. +// Copyright (c) 2023 RobôCIn. +// + +#pragma once + +#include "robocin/geometry/internal/rect2d_internal.h" +#include "robocin/geometry/point2d.h" +#include "robocin/utility/epsilon.h" +#include "robocin/utility/fuzzy_compare.h" + +#include + +namespace robocin { + +template +struct Rect2D { + public: + // Member types ---------------------------------------------------------------------------------- + using value_type = T; + using reference = value_type&; + using size_type = std::size_t; + using iterator = rect2d_internal::iterator; + using const_iterator = rect2d_internal::iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // Friendships ----------------------------------------------------------------------------------- + template + friend struct Rect2D; + + // Members --------------------------------------------------------------------------------------- + Point2D top_left; + value_type width, height; + + // Static constructors --------------------------------------------------------------------------- + static consteval Rect2D create() { return Rect2D(); } + + // Constructors ---------------------------------------------------------------------------------- + constexpr Rect2D() : top_left(0, 0), width{}, height{} {} + constexpr Rect2D(const Rect2D& rect) = default; + constexpr Rect2D(Rect2D&& rect) noexcept = default; + + constexpr Rect2D(const Point2D& top_left, value_type width, value_type height) : + top_left(top_left), + width{width}, + height{height} {} + + constexpr Rect2D(Point2D&& top_left, value_type width, value_type height) : + top_left(top_left), + width{width}, + height{height} {} + + constexpr Rect2D(const Point2D& top_left, const Point2D& bottom_right) : + top_left(top_left), + width{static_cast(bottom_right.x - top_left.x)}, + height{static_cast(bottom_right.y - top_left.y)} {} + + constexpr Rect2D(Point2D&& top_left, Point2D&& bottom_right) : + top_left(top_left), + width{static_cast(bottom_right.x - top_left.x)}, + height{static_cast(bottom_right.y - top_left.y)} {} + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + + constexpr explicit Rect2D(const rect2d_internal::StructRect auto& rect) : + top_left(static_cast>(rect.top_left)), + width{static_cast(rect.width)}, + height{static_cast(rect.height)} {} + constexpr explicit Rect2D(const rect2d_internal::ClassRect auto& rect) : + top_left(static_cast>(rect.TopLeft())), + width{static_cast(rect.Width())}, + height{static_cast(rect.Height())} {} + template + constexpr explicit Rect2D(const std::pair& pair) : + Rect2D(static_cast>(pair.first), + static_cast>(pair.second)) {} + + // Destructor ------------------------------------------------------------------------------------ + constexpr ~Rect2D() = default; + + // Assignment operators -------------------------------------------------------------------------- + constexpr Rect2D& operator=(const Rect2D& other) = default; + constexpr Rect2D& operator=(Rect2D&& other) noexcept = default; + + // Validators ------------------------------------------------------------------------------------ + [[nodiscard]] constexpr bool isNull() const { + if constexpr (has_epsilon_v) { + return fuzzyIsZero(top_left.x) and fuzzyIsZero(top_left.y) and fuzzyIsZero(width) + and fuzzyIsZero(height); + } else { + return top_left.x == 0 and top_left.y == 0 and width == 0 and height == 0; + } + } + + // Arithmetic-assignment operators --------------------------------------------------------------- + inline constexpr Rect2D& operator+=(const Rect2D& other) { + return top_left += other.top_left, width += other.width, height += other.height, *this; + } + inline constexpr Rect2D& operator-=(const Rect2D& other) { + return top_left -= other.top_left, width -= other.width, height -= other.height, *this; + } + inline constexpr Rect2D& operator*=(value_type factor) { + return width *= factor, height *= factor, *this; + } + inline constexpr Rect2D& operator/=(value_type factor) { + return width /= factor, height /= factor, *this; + } + + // Arithmetic operators -------------------------------------------------------------------------- + inline constexpr Rect2D operator+(const Rect2D& other) const { return Rect2D(*this) += other; } + inline constexpr Rect2D operator-(const Rect2D& other) const { return Rect2D(*this) -= other; } + inline constexpr Rect2D operator*(value_type factor) const { return Rect2D(*this) *= factor; } + inline constexpr Rect2D operator/(value_type factor) const { return Rect2D(*this) /= factor; } + + // Arithmetic friend operators ------------------------------------------------------------------- + friend inline constexpr Rect2D operator*(value_type factor, const Rect2D& rect) { + return Rect2D(rect.top_left, rect.width * factor, rect.height * factor); + } + + // Sign operators -------------------------------------------------------------------------------- + inline constexpr Rect2D operator+() const { return *this; } + inline constexpr Rect2D operator-() const { return Rect2D(-top_left, width, height); } + + // Comparison operators -------------------------------------------------------------------------- + inline constexpr bool operator==(const Rect2D& other) const { + if constexpr (has_epsilon_v) { + return fuzzyCmpEqual(top_left.x, other.top_left.x) + and fuzzyCmpEqual(top_left.y, other.top_left.y) and fuzzyCmpEqual(width, other.width) + and fuzzyCmpEqual(height, other.height); + } else { + return top_left.x == other.top_left.x and top_left.y == other.top_left.y + and width == other.width and height == other.height; + } + } + + inline constexpr bool contains(const Point2D& point) const { + return top_left.x <= point.x and point.x <= (top_left.x + width) and point.y >= top_left.y + and point.y <= (top_left.y + height); + } + + inline constexpr bool contains(const Rect2D& rect) const { + return this->contains(rect.top_left) + and this->contains( + Point2D(rect.top_left.x + rect.width, rect.top_left.y + rect.height)); + } + + inline constexpr bool intersects(const Rect2D& other) const { + return this->contains(other.top_left) or other.contains(this->top_left); + } + + // Swap ------------------------------------------------------------------------------------------ + constexpr void swap(Rect2D& other) noexcept { std::swap(*this, other); } + + // Array-like ------------------------------------------------------------------------------------ + [[nodiscard]] static consteval size_type size() { return 4; } + + inline constexpr reference operator[](size_type pos) { + switch (pos) { + case 0: return top_left.x; + case 1: return top_left.y; + case 2: return width; + case 3: return height; + default: throw std::out_of_range("Rect2D operator[]: index out of range."); + } + } + + inline constexpr value_type operator[](size_type pos) const { + switch (pos) { + case 0: return top_left.x; + case 1: return top_left.y; + case 2: return width; + case 3: return height; + default: throw std::out_of_range("Rect2D 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, Rect2D& rect) { + return is >> rect.top_left.x >> rect.top_left.y >> rect.width >> rect.height; + } + + friend inline std::ostream& operator<<(std::ostream& os, const Rect2D& rect) { + return os << "[.top_left = " << rect.top_left << ", .width = " << rect.width + << ", .height = " << rect.height << " ]"; + } +}; + +// Deduction guides -------------------------------------------------------------------------------- +Rect2D()->Rect2D; + +template +Rect2D(T, U) -> Rect2D>; + +} // namespace robocin diff --git a/robocin/geometry/rect2d_test.cpp b/robocin/geometry/rect2d_test.cpp new file mode 100644 index 0000000..f6e3621 --- /dev/null +++ b/robocin/geometry/rect2d_test.cpp @@ -0,0 +1,189 @@ +// +// Created by Marcos Oliveira on 10/22/2023. +// Copyright (c) 2023 RobôCIn. +// + +#include "robocin/geometry/point2d.h" +#include "robocin/geometry/rect2d.h" + +#include +#include + +namespace robocin { + +namespace { +using ::testing::Test; +using ::testing::Types; + +template +class Rect2DTest : public Test {}; + +using TestTypes = Types; +TYPED_TEST_SUITE(Rect2DTest, TestTypes); + +// Static constructors ----------------------------------------------------------------------------- + +TYPED_TEST(Rect2DTest, Create) { + const Rect2D kRect = Rect2D::create(); + + EXPECT_EQ(kRect.top_left.x, 0); + EXPECT_EQ(kRect.top_left.y, 0); + EXPECT_EQ(kRect.width, 0); + EXPECT_EQ(kRect.height, 0); +} + +// Constructors ------------------------------------------------------------------------------------ +TYPED_TEST(Rect2DTest, DefaultConstructor) { + const Rect2D kRect{}; + + EXPECT_EQ(kRect.top_left.x, 0); + EXPECT_EQ(kRect.top_left.y, 0); + EXPECT_EQ(kRect.width, 0); + EXPECT_EQ(kRect.height, 0); +} + +TYPED_TEST(Rect2DTest, CopyConstructor) { + const Rect2D kOther(Point2D(/*x*/ 1, /*y*/ 2), 30, 40); + const Rect2D kRect(kOther); + + EXPECT_EQ(kRect.top_left.x, 1); + EXPECT_EQ(kRect.top_left.y, 2); + EXPECT_EQ(kRect.width, 30); + EXPECT_EQ(kRect.height, 40); +} + +TYPED_TEST(Rect2DTest, MoveConstructor) { + const Rect2D kOther(Point2D(/*x*/ 1, /*y*/ 2), 30, 40); + const Rect2D kRect(std::move(kOther)); + + EXPECT_EQ(kRect.top_left.x, 1); + EXPECT_EQ(kRect.top_left.y, 2); + EXPECT_EQ(kRect.width, 30); + EXPECT_EQ(kRect.height, 40); +} + +TYPED_TEST(Rect2DTest, ConstructorGivenParams) { + const Rect2D kRect(Point2D(/*x*/ 1, /*y*/ 2), 30, 40); + + EXPECT_EQ(kRect.top_left.x, 1); + EXPECT_EQ(kRect.top_left.y, 2); + EXPECT_EQ(kRect.width, 30); + EXPECT_EQ(kRect.height, 40); +} + +TYPED_TEST(Rect2DTest, ConstructorGivenTopAndBottom) { + const Rect2D kRect(Point2D(/*x*/ 10, /*y*/ 0), + Point2D(/*x*/ 110, /*y*/ 100)); + + EXPECT_EQ(kRect.top_left.x, 10); + EXPECT_EQ(kRect.top_left.y, 0); + EXPECT_EQ(kRect.width, 100); + EXPECT_EQ(kRect.height, 100); +} + +TYPED_TEST(Rect2DTest, MoveConstructorGivenTopAndBottom) { + const Rect2D kOther(Point2D(/*x*/ 10, /*y*/ 0), + Point2D(/*x*/ 110, /*y*/ 100)); + const Rect2D kRect(std::move(kOther)); + + EXPECT_EQ(kRect.top_left.x, 10); + EXPECT_EQ(kRect.top_left.y, 0); + EXPECT_EQ(kRect.width, 100); + EXPECT_EQ(kRect.height, 100); +} + +TYPED_TEST(Rect2DTest, ConstructorGivenStructRect) { + struct SomePairStruct { + TypeParam x; + TypeParam y; + }; + + struct OtherRect2D { + + SomePairStruct top_left; + TypeParam width; + TypeParam height; + }; + + const OtherRect2D kOtherRect2d{.top_left = SomePairStruct{.x = 0, .y = 1}, + .width = 10, + .height = 20}; + const Rect2D kRect(kOtherRect2d); + + EXPECT_EQ(kRect.top_left.x, 0); + EXPECT_EQ(kRect.top_left.y, 1); + EXPECT_EQ(kRect.width, 10); + EXPECT_EQ(kRect.height, 20); +} + +// Validators -------------------------------------------------------------------------------------- + +TYPED_TEST(Rect2DTest, IsNull) { + const Rect2D kRect(Point2D(/*x*/ 0, /*y*/ 0), 0, 0); + EXPECT_TRUE(kRect.isNull()); + + const Rect2D kRect2(Point2D(/*x*/ 1, /*y*/ 2), 30, 40); + EXPECT_FALSE(kRect2.isNull()); +} + +// Comparison operators -------------------------------------------------------------------------- + +TYPED_TEST(Rect2DTest, IsEq) { + const Rect2D kRect(Point2D(/*x*/ 0, /*y*/ 0), 0, 0); + EXPECT_TRUE(kRect == kRect); + const Rect2D kRect2(Point2D(/*x*/ 1, /*y*/ 2), 30, 40); + EXPECT_TRUE(kRect2 == kRect2); + + EXPECT_FALSE(kRect == kRect2); + + const Rect2D kRect3(Point2D(/*x*/ 0, /*y*/ 0), 0, 0); + EXPECT_TRUE(kRect == kRect3); +} + +TYPED_TEST(Rect2DTest, ContainsPoint) { + const Rect2D kRect(Point2D(/*x*/ 0, /*y*/ 0), 10, 10); + const Point2D kPoint(/*x*/ 0, /*y*/ 0); + EXPECT_TRUE(kRect.contains(kPoint)); + const Point2D kPoint2(/*x*/ 11, /*y*/ 11); + EXPECT_FALSE(kRect.contains(kPoint2)); +} + +TYPED_TEST(Rect2DTest, ContainsRect) { + const Rect2D kRect(Point2D(/*x*/ 0, /*y*/ 0), 10, 10); + const Rect2D kRect2(Point2D(/*x*/ 5, /*y*/ 5), 2, 2); + EXPECT_TRUE(kRect.contains(kRect2)); + EXPECT_FALSE(kRect2.contains(kRect)); +} + +TYPED_TEST(Rect2DTest, Intersects) { + const Rect2D kRect(Point2D(/*x*/ 0, /*y*/ 0), 10, 10); + const Rect2D kRect2(Point2D(/*x*/ 5, /*y*/ 5), 2, 2); + const Rect2D kRect3(Point2D(/*x*/ 10, /*y*/ 10), 10, 10); + + EXPECT_TRUE(kRect.intersects(kRect2) == kRect2.intersects(kRect)); + + EXPECT_TRUE(kRect.intersects(kRect2)); + EXPECT_TRUE(kRect.intersects(kRect3)); + EXPECT_FALSE(kRect3.intersects(kRect2)); +} + +// Swap -------------------------------------------------------------------------------------------- + +TYPED_TEST(Rect2DTest, Swapping) { + Rect2D rect1(Point2D(/*x*/ 1, /*y*/ 2), 30, 40); + Rect2D rect2(Point2D(/*x*/ 2, /*y*/ 3), 45, 20); + + rect1.swap(rect2); + + EXPECT_EQ(rect1.top_left.x, 2); + EXPECT_EQ(rect1.top_left.y, 3); + EXPECT_EQ(rect1.width, 45); + EXPECT_EQ(rect1.height, 20); + EXPECT_EQ(rect2.top_left.x, 1); + EXPECT_EQ(rect2.top_left.y, 2); + EXPECT_EQ(rect2.width, 30); + EXPECT_EQ(rect2.height, 40); +} + +} // namespace +} // namespace robocin