From 895cddc73909c821416a7517898ed0c6da654eac Mon Sep 17 00:00:00 2001 From: tovjemam Date: Wed, 3 Dec 2025 20:45:32 +0100 Subject: [PATCH] Add comments, change renderers to have Save instead of saving on destruction --- .clang-format | 5 +- CMakeLists.txt | 2 +- input_file.cpp | 93 ++++++++++++++++++++++++++++---------- input_file.hpp | 14 +++--- main.cpp | 35 ++++++++++++-- math/constants.hpp | 15 +++++- math/transforms.cpp | 2 +- math/transforms.hpp | 26 ++++++++++- math/vector.hpp | 43 ++++++------------ renderers/bitmap.hpp | 45 ++++++------------ renderers/pgm_renderer.cpp | 44 ++++++++---------- renderers/pgm_renderer.hpp | 23 ++++++---- renderers/renderer.hpp | 7 ++- renderers/svg_renderer.cpp | 40 ++++++++++------ renderers/svg_renderer.hpp | 22 ++++++--- shapes/circle.hpp | 26 +++++------ shapes/group.hpp | 75 +++++++++++------------------- shapes/line.hpp | 15 +++--- shapes/rectangle.hpp | 32 +++++-------- shapes/shape.hpp | 5 ++ 20 files changed, 322 insertions(+), 247 deletions(-) diff --git a/.clang-format b/.clang-format index 0836977..869dfe7 100644 --- a/.clang-format +++ b/.clang-format @@ -3,4 +3,7 @@ DerivePointerAlignment: false PointerAlignment: Left IndentWidth: 4 # spaces per indent level TabWidth: 4 # width of a tab character -UseTab: Never # options: Never, ForIndentation, Alwayss \ No newline at end of file +UseTab: Never # options: Never, ForIndentation, Alwayss +AccessModifierOffset: -4 +AllowShortFunctionsOnASingleLine: Inline +BreakTemplateDeclarations: Yes diff --git a/CMakeLists.txt b/CMakeLists.txt index ba37e21..c910385 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) project(CppDrawing) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) add_executable(drawing "math/transforms.cpp" diff --git a/input_file.cpp b/input_file.cpp index 8ccfcd8..fd65db1 100644 --- a/input_file.cpp +++ b/input_file.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "shapes/circle.hpp" #include "shapes/line.hpp" @@ -19,58 +20,84 @@ InputFile::InputFile(const std::filesystem::path& path) : m_file(path), m_cmdsPr } } +// istream >> operator for math::Vector static std::istream& operator>>(std::istream& is, math::Vector& vec) { return is >> vec.x >> vec.y; } +// command name -> command handler map using CommandMap = std::map>; -template static const char* GetTypeName() +/** + * @brief Get the type name as a string + * + * @tparam T Type + * @return Type name + */ +template +static const char* GetTypeName() { return typeid(T).name(); } -template <> const char* GetTypeName() +template <> +const char* GetTypeName() { return "integer"; } -template <> const char* GetTypeName() +template <> +const char* GetTypeName() { return "float"; } -template <> const char* GetTypeName() +template <> +const char* GetTypeName() { return "vector"; } -template <> const char* GetTypeName() +template <> +const char* GetTypeName() { return "string"; } -template static T ReadVal(std::istream& is) +/** + * @brief Read a value from an `istream` and check for errors + * + * @tparam T Type of the value to read + * @param is Input stream + * @return Read value + */ +template +static T ReadVal(std::istream& is) { T v; is >> v; - // std::cout << "Reading " << GetTypeName() << ' ' << v << std::endl; - if (is.bad()) - { - throw std::runtime_error("Could not parse " + std::string(GetTypeName()) + " from stream"); - } + throw std::runtime_error(std::format("Could not parse {} from stream", GetTypeName())); return v; } +/** + * @brief Register a command in a command map + * + * @tparam TArgs Argument types + * @tparam THandler Handler type + * @param cmdMap Command map + * @param cmd Command name + * @param handler Command handler + */ template static void RegisterCommand(CommandMap& cmdMap, const std::string& cmd, THandler&& handler) { cmdMap[cmd] = [handler = std::forward(handler)](std::istream& is) { - std::apply(handler, std::tuple{ReadVal(is)...}); // pres tuple kvuli poradi evaluace... + std::apply(handler, std::tuple{ReadVal(is)...}); // tuple because of order of evaluation... }; } @@ -104,8 +131,9 @@ shapes::Group InputFile::Parse() RegisterCommand(cmds, "translate", [&shapes](int ox, int oy) { shapes.Translate(math::Vector{ox, oy}); }); - RegisterCommand( - cmds, "rotate", [&shapes](int cx, int cy, float deg) { shapes.Rotate(math::Vector{cx, cy}, deg * math::DEG_TO_RAD); }); + RegisterCommand(cmds, "rotate", [&shapes](int cx, int cy, float deg) { + shapes.Rotate(math::Vector{cx, cy}, deg * math::DEG_TO_RAD); + }); RegisterCommand(cmds, "scale", [&shapes](int cx, int cy, float factor) { if (factor < std::numeric_limits().epsilon()) @@ -114,24 +142,39 @@ shapes::Group InputFile::Parse() shapes.Scale(math::Vector{cx, cy}, factor); }); + /* ---------------------------- */ + std::string line; + size_t lineNum = 0; - while (std::getline(m_file, line)) + try { - // TODO: find # mid line - if (line.empty() || line[0] == '#') - continue; + while (std::getline(m_file, line)) + { + ++lineNum; - std::istringstream iss(line); - std::string cmdName = ReadVal(iss); + if (line.empty() || line[0] == '#') + continue; - auto cmd = cmds.find(cmdName); + auto commentPos = line.find('#'); + if (commentPos != std::string::npos) + line = line.substr(0, commentPos); - if (cmd == cmds.end()) - throw std::runtime_error("Unknown command: " + cmdName); + std::istringstream iss(line); + std::string cmdName = ReadVal(iss); - cmd->second(iss); - ++m_cmdsProcessed; + auto cmd = cmds.find(cmdName); + + if (cmd == cmds.end()) + throw std::runtime_error("Unknown command: " + cmdName); + + cmd->second(iss); + ++m_cmdsProcessed; + } + } + catch (const std::exception& e) + { + throw std::runtime_error(std::format("Line {}: {}", lineNum, e.what())); } return shapes; diff --git a/input_file.hpp b/input_file.hpp index 56b2362..6629f28 100644 --- a/input_file.hpp +++ b/input_file.hpp @@ -5,19 +5,21 @@ #include "shapes/group.hpp" +/** + * @brief Input file parser + * + * Parses an input file containing drawing commands and constructs a `Group` of shapes. + */ class InputFile { - public: +public: InputFile(const std::filesystem::path& path); shapes::Group Parse(); - size_t GetNumProcessedCmds() const - { - return m_cmdsProcessed; - } + size_t GetNumProcessedCmds() const { return m_cmdsProcessed; } - private: +private: std::ifstream m_file; size_t m_cmdsProcessed; }; diff --git a/main.cpp b/main.cpp index ec9cf36..7143ef5 100644 --- a/main.cpp +++ b/main.cpp @@ -8,13 +8,30 @@ #include "renderers/svg_renderer.hpp" #include +/** + * @brief Render shapes using a specific renderer and save to a file + * + * @tparam T Renderer type + * @param shapes Shapes to render + * @param path Output file path + * @param width Width + * @param height Height + */ template void Render(const shapes::Group& shapes, const std::filesystem::path& path, size_t width, size_t height) { - T renderer(path, width, height); + T renderer(width, height); shapes.Draw(renderer); + renderer.Save(path); } +/** + * @brief Parse a single dimension from a string + * + * @param start Pointer to the start of the dimension string + * @param end Pointer to the end of the dimension string + * @return Parsed dimension + */ static size_t ParseDim(const char* start, const char* end) { size_t val; @@ -28,14 +45,18 @@ static size_t ParseDim(const char* start, const char* end) return val; } +/** + * Parse a size string + * + * @param sizeStr Size string in the format of `x` + * @return Tuple of width and height + */ static std::tuple ParseSize(const std::string& sizeStr) { auto xPos = sizeStr.find('x'); if (xPos == std::string::npos) - { throw std::runtime_error("Size must be in format of x"); - } size_t width = ParseDim(sizeStr.data(), sizeStr.data() + xPos); size_t height = ParseDim(sizeStr.data() + xPos + 1, sizeStr.data() + sizeStr.size()); @@ -43,6 +64,9 @@ static std::tuple ParseSize(const std::string& sizeStr) return std::make_tuple(width, height); } +/** + * @brief Run the drawing process + */ static void Run(const std::string& inputFile, const std::string& outputFile, const std::string& sizeStr) { try @@ -56,7 +80,7 @@ static void Run(const std::string& inputFile, const std::string& outputFile, con Render(shapes, outputFile, width, height); else Render(shapes, outputFile, width, height); - + std::cout << "OK" << std::endl; std::cout << file.GetNumProcessedCmds() << std::endl; } @@ -66,6 +90,9 @@ static void Run(const std::string& inputFile, const std::string& outputFile, con } } +/** + * @brief Main entry point + */ int main(int argc, char** argv) { if (argc < 4) diff --git a/math/constants.hpp b/math/constants.hpp index 75d4265..08334ff 100644 --- a/math/constants.hpp +++ b/math/constants.hpp @@ -1,10 +1,23 @@ #pragma once +#include + namespace math { -constexpr float PI = 3.14159265358979323846f; +/** + * pi + */ +constexpr float PI = std::numbers::pi_v; + +/** + * Degrees to radians conversion factor + */ constexpr float DEG_TO_RAD = PI / 180.0f; + +/** + * Radians to degrees conversion factor + */ constexpr float RAD_TO_DEG = 1.0f / DEG_TO_RAD; } // namespace math diff --git a/math/transforms.cpp b/math/transforms.cpp index a24b4f3..3e3610c 100644 --- a/math/transforms.cpp +++ b/math/transforms.cpp @@ -1,6 +1,6 @@ #include "transforms.hpp" -#include #include "constants.hpp" +#include math::Vector math::RotatePoint(const Vector& center, float angle, const Vector& p) { diff --git a/math/transforms.hpp b/math/transforms.hpp index 27f48ac..4ace094 100644 --- a/math/transforms.hpp +++ b/math/transforms.hpp @@ -5,9 +5,33 @@ namespace math { +/** + * Rotate a point `p` around a center point `center` by `angle` radians. + * + * @param center The center point to rotate around + * @param angle The angle in radians to rotate + * @param p The point to be rotated + * @return The rotated point + */ Vector RotatePoint(const Vector& center, float angle, const Vector& p); + +/** + * Scale a point `p` relative to a center point `center` by a `factor`. + * + * @param center The center point to scale relative to + * @param factor The scaling factor + * @param p The point to be scaled + * @return The scaled point + */ Vector ScalePoint(const Vector& center, float factor, const Vector& p); + +/** + * Rotate an angle by another angle + * + * @param originalAngle The original angle in radians + * @param rotationAngle The angle in radians to rotate by + * @return The new angle in radians in range [0, 2*PI) + */ float RotateAngle(float originalAngle, float rotationAngle); - } // namespace math \ No newline at end of file diff --git a/math/vector.hpp b/math/vector.hpp index 02d8012..250b7ac 100644 --- a/math/vector.hpp +++ b/math/vector.hpp @@ -1,32 +1,21 @@ #pragma once -#include - namespace math { +/** + * @brief 2D Vector + * + * Represents a point or a vector in 2D. Provides basic vector operations. + */ class Vector { - public: - Vector() : x{0.0f}, y{0.0f} - { - } +public: + Vector() : x{0.0f}, y{0.0f} {} + Vector(float x, float y) : x{x}, y{y} {} + Vector(int x, int y) : x{static_cast(x)}, y{static_cast(y)} {} - Vector(float x, float y) : x{x}, y{y} - { - } - - Vector(int x, int y) : x{static_cast(x)}, y{static_cast(y)} - { - } - - // float& operator[](size_t idx) { return m_v[idx]; } - // const float& operator[](size_t idx) const { return m_v[idx]; } - - Vector operator+(const Vector& other) const - { - return Vector(x + other.x, y + other.y); - } + Vector operator+(const Vector& other) const { return Vector(x + other.x, y + other.y); } Vector& operator+=(const Vector& other) { @@ -35,10 +24,7 @@ class Vector return *this; } - Vector operator-(const Vector& other) const - { - return Vector(x - other.x, y - other.y); - } + Vector operator-(const Vector& other) const { return Vector(x - other.x, y - other.y); } Vector& operator-=(const Vector& other) { @@ -47,10 +33,7 @@ class Vector return *this; } - Vector operator*(float factor) const - { - return Vector(x * factor, y * factor); - } + Vector operator*(float factor) const { return Vector(x * factor, y * factor); } Vector& operator*=(float factor) { @@ -59,7 +42,7 @@ class Vector return *this; } - public: +public: float x, y; }; diff --git a/renderers/bitmap.hpp b/renderers/bitmap.hpp index b1f83b0..ccd2ff7 100644 --- a/renderers/bitmap.hpp +++ b/renderers/bitmap.hpp @@ -6,14 +6,10 @@ class Color { - public: - Color() : l{0} - { - } +public: + Color() : l{0} {} - Color(uint8_t l) : l{l} - { - } + Color(uint8_t l) : l{l} {} uint8_t l{}; // luminence @@ -23,18 +19,18 @@ class Color // l = alpha; } - private: +private: static uint8_t BlendChannel(uint8_t a, uint8_t b, uint8_t alpha) { - return static_cast((static_cast(a) * (255 - static_cast(alpha)) + - static_cast(b) * static_cast(alpha)) / - 255); + return static_cast( + (static_cast(a) * (255 - static_cast(alpha)) + static_cast(b) * static_cast(alpha)) / + 255); } }; class Bitmap { - public: +public: Bitmap(size_t width, size_t height, const Color& clearColor) : m_width(width), m_height(height), m_data(width * height, clearColor) { @@ -50,36 +46,23 @@ class Bitmap // return {&m_data[row * m_width], m_width}; // }; - Color* operator[](size_t row) - { - return &m_data[row * m_width]; - }; + Color* operator[](size_t row) { return &m_data[row * m_width]; }; - const Color* operator[](size_t row) const - { - return &m_data[row * m_width]; - }; + const Color* operator[](size_t row) const { return &m_data[row * m_width]; }; - size_t GetWidth() const - { - return m_width; - } + size_t GetWidth() const { return m_width; } - size_t GetHeight() const - { - return m_height; - } + size_t GetHeight() const { return m_height; } void Put(int x, int y, const Color& color, uint8_t alpha) { if (x < 0 || y < 0 || x >= m_width || y >= m_height) return; // out of bounds - (*this)[y][x].Blend(color, alpha); - + (*this)[y][x].Blend(color, alpha); } - private: +private: size_t m_width, m_height; std::vector m_data; }; \ No newline at end of file diff --git a/renderers/pgm_renderer.cpp b/renderers/pgm_renderer.cpp index ed91c6a..85a1be7 100644 --- a/renderers/pgm_renderer.cpp +++ b/renderers/pgm_renderer.cpp @@ -1,15 +1,9 @@ #include "pgm_renderer.hpp" #include +#include #include -PgmRenderer::PgmRenderer(const std::filesystem::path& path, size_t width, size_t height) - : m_file(path), m_bitmap(width, height, Color{0xFF}) -{ - if (m_file.bad()) - { - throw std::runtime_error("Could not open file for writing: " + path.string()); - } -} +PgmRenderer::PgmRenderer(size_t width, size_t height) : m_bitmap(width, height, Color{0xFF}) {} void PgmRenderer::DrawLine(const math::Vector& p0, const math::Vector& p1) { @@ -29,10 +23,10 @@ void PgmRenderer::DrawRectangle(const math::Vector& pos, const math::Vector& siz const auto pB = pA + bX * size.x; const auto pC = pD + bX * size.x; - PgmRenderer::DrawLine(pA, pB); - PgmRenderer::DrawLine(pB, pC); - PgmRenderer::DrawLine(pC, pD); - PgmRenderer::DrawLine(pD, pA); + DrawLine(pA, pB); + DrawLine(pB, pC); + DrawLine(pC, pD); + DrawLine(pD, pA); } void PgmRenderer::DrawCircle(const math::Vector& center, float radius) @@ -41,26 +35,28 @@ void PgmRenderer::DrawCircle(const math::Vector& center, float radius) RasterizeCircle(center.x, center.y, radius); } -PgmRenderer::~PgmRenderer() +void PgmRenderer::Save(const std::filesystem::path& path) { - Flush(); -} + std::ofstream file{path}; -void PgmRenderer::Flush() -{ - m_file << "P2" << std::endl; - m_file << "# KIV/CPP" << std::endl; - m_file << m_bitmap.GetWidth() << ' ' << m_bitmap.GetHeight() << std::endl; - m_file << 255 << std::endl; + if (!file.is_open()) + { + throw std::runtime_error{"Cannot open file for writing: " + path.string()}; + } + + file << "P2" << std::endl; + file << "# KIV/CPP" << std::endl; + file << m_bitmap.GetWidth() << ' ' << m_bitmap.GetHeight() << std::endl; + file << 255 << std::endl; for (size_t y = 0; y < m_bitmap.GetHeight(); ++y) { for (size_t x = 0; x < m_bitmap.GetWidth(); ++x) { - m_file << static_cast(m_bitmap[y][x].l) << ' '; + file << static_cast(m_bitmap[y][x].l) << ' '; } - m_file << std::endl; + file << std::endl; } } @@ -87,7 +83,7 @@ void PgmRenderer::RasterizeLine(int x0, int y0, int x1, int y1) ox = 1; } - while (1) + while (true) { // draw a square of pixels for thickness for (int i = -w / 2; i <= w / 2; i++) diff --git a/renderers/pgm_renderer.hpp b/renderers/pgm_renderer.hpp index 9b80188..3361237 100644 --- a/renderers/pgm_renderer.hpp +++ b/renderers/pgm_renderer.hpp @@ -2,28 +2,31 @@ #include #include -#include -#include "renderer.hpp" #include "bitmap.hpp" +#include "renderer.hpp" -class PgmRenderer : public Renderer +/** + * @brief PGM Renderer + * + * Draws shapes to a PGM format. + */ +class PgmRenderer final : public Renderer { - public: - PgmRenderer(const std::filesystem::path& path, size_t width, size_t height); +public: + PgmRenderer(size_t width, size_t height); void DrawLine(const math::Vector& p0, const math::Vector& p1) override; void DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) override; void DrawCircle(const math::Vector& center, float radius) override; - ~PgmRenderer() override; + void Save(const std::filesystem::path& path); - private: - std::ofstream m_file; + ~PgmRenderer() override = default; + +private: Bitmap m_bitmap; void RasterizeLine(int x0, int y0, int x1, int y1); void RasterizeCircle(int xm, int ym, int r); - - void Flush(); }; diff --git a/renderers/renderer.hpp b/renderers/renderer.hpp index 12f3ed4..64479cb 100644 --- a/renderers/renderer.hpp +++ b/renderers/renderer.hpp @@ -1,9 +1,14 @@ #pragma once #include "math/vector.hpp" +/** + * @brief Renderer interface + * + * Represents an inteface for different renderers that can draw basic shapes. + */ class Renderer { - public: +public: virtual void DrawLine(const math::Vector& p0, const math::Vector& p1) = 0; virtual void DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) = 0; virtual void DrawCircle(const math::Vector& center, float radius) = 0; diff --git a/renderers/svg_renderer.cpp b/renderers/svg_renderer.cpp index e2c33d1..138a3fb 100644 --- a/renderers/svg_renderer.cpp +++ b/renderers/svg_renderer.cpp @@ -1,38 +1,48 @@ #include "svg_renderer.hpp" #include "math/constants.hpp" +#include -SvgRenderer::SvgRenderer(const std::filesystem::path& path, size_t width, size_t height) : m_file(path) +SvgRenderer::SvgRenderer(size_t width, size_t height) : m_width{width}, m_height{height} { - if (m_file.bad()) - throw std::runtime_error("Cannot open " + path.string() + " for writing"); - - m_file << "" << std::endl; + // white bg + m_out << " " << std::endl; } void SvgRenderer::DrawLine(const math::Vector& p0, const math::Vector& p1) { - m_file << " " << std::endl; + m_out << " " << std::endl; } void SvgRenderer::DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) { float angleDeg = angle * math::RAD_TO_DEG; - m_file << " " << std::endl; + m_out << " " << std::endl; } void SvgRenderer::DrawCircle(const math::Vector& center, float radius) { - m_file << " " << std::endl; + m_out << " " << std::endl; } -SvgRenderer::~SvgRenderer() +void SvgRenderer::Save(const std::filesystem::path& path) { - m_file << "" << std::endl; + std::ofstream file{path}; + + if (!file.is_open()) + { + throw std::runtime_error{"Cannot open file for writing: " + path.string()}; + } + + file << "" << std::endl; + + file << m_out.str(); + + file << "" << std::endl; } diff --git a/renderers/svg_renderer.hpp b/renderers/svg_renderer.hpp index a2bfd99..479e659 100644 --- a/renderers/svg_renderer.hpp +++ b/renderers/svg_renderer.hpp @@ -3,19 +3,27 @@ #include "renderer.hpp" #include -#include +#include -class SvgRenderer : public Renderer +/** + * @brief SVG Renderer + * + * Draws shapes to an SVG format. + */ +class SvgRenderer final : public Renderer { - public: - SvgRenderer(const std::filesystem::path& path, size_t width, size_t height); +public: + SvgRenderer(size_t width, size_t height); void DrawLine(const math::Vector& p0, const math::Vector& p1) override; void DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) override; void DrawCircle(const math::Vector& center, float radius) override; - ~SvgRenderer() override; + void Save(const std::filesystem::path& path); - private: - std::ofstream m_file; + ~SvgRenderer() override = default; + +private: + size_t m_width, m_height; + std::ostringstream m_out; }; \ No newline at end of file diff --git a/shapes/circle.hpp b/shapes/circle.hpp index da6b3ca..cd4b044 100644 --- a/shapes/circle.hpp +++ b/shapes/circle.hpp @@ -5,22 +5,18 @@ namespace shapes { -class Circle : public Shape +/** + * @brief Circle shape + * + * Represents a circle defined by a center point and a radius. + */ +class Circle final : public Shape { - public: - Circle(const math::Vector& center, float radius) : m_center(center), m_radius(radius) - { - } +public: + Circle(const math::Vector& center, float radius) : m_center(center), m_radius(radius) {} - const math::Vector& GetCenter() const - { - return m_center; - } - - float GetRadius() const - { - return m_radius; - } + const math::Vector& GetCenter() const { return m_center; } + float GetRadius() const { return m_radius; } void Translate(const math::Vector& offset) override; void Rotate(const math::Vector& center, float angle) override; @@ -30,7 +26,7 @@ class Circle : public Shape ~Circle() override = default; - private: +private: math::Vector m_center; float m_radius; }; diff --git a/shapes/group.hpp b/shapes/group.hpp index 432e064..035d263 100644 --- a/shapes/group.hpp +++ b/shapes/group.hpp @@ -8,67 +8,46 @@ namespace shapes { +/** + * @brief Group shape + * + * Represents a group of shapes that can be used as a canvas + */ class Group : public Shape { - public: +public: Group() = default; - Group(Group&& other) : m_shapes{std::move(other.m_shapes)} - { - } + Group(const Group&) = delete; + Group& operator=(const Group&) = delete; + Group(Group&&) = default; + Group& operator=(Group&&) = default; - Group& operator=(Group&& other) - { - // TODO: overit - m_shapes = std::move(other.m_shapes); - return *this; - } - - template void AddShape(TArgs&&... args) + template + requires std::derived_from + void AddShape(TArgs&&... args) { m_shapes.emplace_back(std::make_unique(std::forward(args)...)); } - void Translate(const math::Vector& offset) override - { - ShapesCall<&Shape::Translate>(offset); - } - - void Rotate(const math::Vector& center, float angle) override - { - ShapesCall<&Shape::Rotate>(center, angle); - } - - void Scale(const math::Vector& center, float factor) override - { - ShapesCall<&Shape::Scale>(center, factor); - } - - void Draw(Renderer& renderer) const override - { - ShapesCall<&Shape::Draw>(renderer); - } + void Translate(const math::Vector& offset) override { ShapesCall<&Shape::Translate>(offset); } + void Rotate(const math::Vector& center, float angle) override { ShapesCall<&Shape::Rotate>(center, angle); } + void Scale(const math::Vector& center, float factor) override { ShapesCall<&Shape::Scale>(center, factor); } + void Draw(Renderer& renderer) const override { ShapesCall<&Shape::Draw>(renderer); } ~Group() override = default; - private: +private: + // calls a member function on all shapes in the group + template + void ShapesCall(this TSelf&& self, TArgs&&... args) + { + for (auto& shape : self.m_shapes) + (shape.get()->*Fun)(std::forward(args)...); + } + +private: std::vector> m_shapes; - - template void ShapesCall(TArgs&&... args) - { - for (auto& shape : m_shapes) - { - (shape.get()->*TFun)(std::forward(args)...); - } - } - - template void ShapesCall(TArgs&&... args) const - { - for (const auto& shape : m_shapes) - { - (shape.get()->*TFun)(std::forward(args)...); - } - } }; } // namespace shapes \ No newline at end of file diff --git a/shapes/line.hpp b/shapes/line.hpp index d1e36ce..cae9a4b 100644 --- a/shapes/line.hpp +++ b/shapes/line.hpp @@ -6,12 +6,15 @@ namespace shapes { -class Line : public Shape +/** + * @brief Line shape + * + * Represents a line defined by two points. + */ +class Line final : public Shape { - public: - Line(const math::Vector& p0, const math::Vector& p1) : m_p0(p0), m_p1(p1) - { - } +public: + Line(const math::Vector& p0, const math::Vector& p1) : m_p0(p0), m_p1(p1) {} void Translate(const math::Vector& offset) override; void Rotate(const math::Vector& center, float angle) override; @@ -19,7 +22,7 @@ class Line : public Shape void Draw(Renderer& renderer) const override; - private: +private: math::Vector m_p0; math::Vector m_p1; }; diff --git a/shapes/rectangle.hpp b/shapes/rectangle.hpp index 8f90306..fc247ff 100644 --- a/shapes/rectangle.hpp +++ b/shapes/rectangle.hpp @@ -6,27 +6,19 @@ namespace shapes { -class Rectangle : public Shape +/** + * @brief Rectangle shape + * + * Represents a rectangle defined by a position, size and angle of rotation around it's left-top corner + */ +class Rectangle final : public Shape { - public: - Rectangle(const math::Vector& pos, const math::Vector& size) : m_pos(pos), m_size(size), m_angle(0.0f) - { - } +public: + Rectangle(const math::Vector& pos, const math::Vector& size) : m_pos(pos), m_size(size), m_angle(0.0f) {} - const math::Vector& GetPosition() const - { - return m_pos; - } - - const math::Vector& GetSize() const - { - return m_size; - } - - const float GetAngle() const - { - return m_angle; - } + const math::Vector& GetPosition() const { return m_pos; } + const math::Vector& GetSize() const { return m_size; } + const float GetAngle() const { return m_angle; } void Translate(const math::Vector& offset) override; void Rotate(const math::Vector& center, float angle) override; @@ -36,7 +28,7 @@ class Rectangle : public Shape ~Rectangle() override = default; - private: +private: math::Vector m_pos; math::Vector m_size; float m_angle; diff --git a/shapes/shape.hpp b/shapes/shape.hpp index 05fe6f0..14311c2 100644 --- a/shapes/shape.hpp +++ b/shapes/shape.hpp @@ -7,6 +7,11 @@ class Renderer; namespace shapes { +/** + * @brief Shape interface + * + * Interface for 2d shapes that can be transformed and drawn using a Renderer. + */ class Shape { public: