Add comments, change renderers to have Save instead of saving on destruction

This commit is contained in:
tovjemam 2025-12-03 20:45:32 +01:00
parent e5bf048d21
commit 895cddc739
20 changed files with 322 additions and 247 deletions

View File

@ -4,3 +4,6 @@ PointerAlignment: Left
IndentWidth: 4 # spaces per indent level
TabWidth: 4 # width of a tab character
UseTab: Never # options: Never, ForIndentation, Alwayss
AccessModifierOffset: -4
AllowShortFunctionsOnASingleLine: Inline
BreakTemplateDeclarations: Yes

View File

@ -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"

View File

@ -4,6 +4,7 @@
#include <iostream>
#include <map>
#include <string>
#include <format>
#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<std::string, std::function<void(std::istream&)>>;
template <class T> static const char* GetTypeName()
/**
* @brief Get the type name as a string
*
* @tparam T Type
* @return Type name
*/
template <class T>
static const char* GetTypeName()
{
return typeid(T).name();
}
template <> const char* GetTypeName<int>()
template <>
const char* GetTypeName<int>()
{
return "integer";
}
template <> const char* GetTypeName<float>()
template <>
const char* GetTypeName<float>()
{
return "float";
}
template <> const char* GetTypeName<math::Vector>()
template <>
const char* GetTypeName<math::Vector>()
{
return "vector";
}
template <> const char* GetTypeName<std::string>()
template <>
const char* GetTypeName<std::string>()
{
return "string";
}
template <class T> 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 <class T>
static T ReadVal(std::istream& is)
{
T v;
is >> v;
// std::cout << "Reading " << GetTypeName<T>() << ' ' << v << std::endl;
if (is.bad())
{
throw std::runtime_error("Could not parse " + std::string(GetTypeName<T>()) + " from stream");
}
throw std::runtime_error(std::format("Could not parse {} from stream", GetTypeName<T>()));
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 <class... TArgs, class THandler>
static void RegisterCommand(CommandMap& cmdMap, const std::string& cmd, THandler&& handler)
{
cmdMap[cmd] = [handler = std::forward<THandler>(handler)](std::istream& is) {
std::apply(handler, std::tuple<TArgs...>{ReadVal<TArgs>(is)...}); // pres tuple kvuli poradi evaluace...
std::apply(handler, std::tuple<TArgs...>{ReadVal<TArgs>(is)...}); // tuple because of order of evaluation...
};
}
@ -104,8 +131,9 @@ shapes::Group InputFile::Parse()
RegisterCommand<int, int>(cmds, "translate", [&shapes](int ox, int oy) { shapes.Translate(math::Vector{ox, oy}); });
RegisterCommand<int, int, float>(
cmds, "rotate", [&shapes](int cx, int cy, float deg) { shapes.Rotate(math::Vector{cx, cy}, deg * math::DEG_TO_RAD); });
RegisterCommand<int, int, float>(cmds, "rotate", [&shapes](int cx, int cy, float deg) {
shapes.Rotate(math::Vector{cx, cy}, deg * math::DEG_TO_RAD);
});
RegisterCommand<int, int, float>(cmds, "scale", [&shapes](int cx, int cy, float factor) {
if (factor < std::numeric_limits<decltype(factor)>().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<std::string>(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<std::string>(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;

View File

@ -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;
};

View File

@ -8,13 +8,30 @@
#include "renderers/svg_renderer.hpp"
#include <charconv>
/**
* @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 <class T>
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 `<width>x<height>`
* @return Tuple of width and height
*/
static std::tuple<size_t, size_t> 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 <width>x<height>");
}
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<size_t, size_t> 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
@ -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)

View File

@ -1,10 +1,23 @@
#pragma once
#include <numbers>
namespace math
{
constexpr float PI = 3.14159265358979323846f;
/**
* pi
*/
constexpr float PI = std::numbers::pi_v<float>;
/**
* 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

View File

@ -1,6 +1,6 @@
#include "transforms.hpp"
#include <cmath>
#include "constants.hpp"
#include <cmath>
math::Vector math::RotatePoint(const Vector& center, float angle, const Vector& p)
{

View File

@ -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

View File

@ -1,32 +1,21 @@
#pragma once
#include <cstddef>
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<float>(x)}, y{static_cast<float>(y)} {}
Vector(float x, float y) : x{x}, y{y}
{
}
Vector(int x, int y) : x{static_cast<float>(x)}, y{static_cast<float>(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;
};

View File

@ -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<uint8_t>((static_cast<int>(a) * (255 - static_cast<int>(alpha)) +
static_cast<int>(b) * static_cast<int>(alpha)) /
255);
return static_cast<uint8_t>(
(static_cast<int>(a) * (255 - static_cast<int>(alpha)) + static_cast<int>(b) * static_cast<int>(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,25 +46,13 @@ 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)
{
@ -76,10 +60,9 @@ class Bitmap
return; // out of bounds
(*this)[y][x].Blend(color, alpha);
}
private:
private:
size_t m_width, m_height;
std::vector<Color> m_data;
};

View File

@ -1,15 +1,9 @@
#include "pgm_renderer.hpp"
#include <cmath>
#include <fstream>
#include <stdexcept>
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<int>(m_bitmap[y][x].l) << ' ';
file << static_cast<int>(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++)

View File

@ -2,28 +2,31 @@
#include <cstddef>
#include <filesystem>
#include <fstream>
#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();
};

View File

@ -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;

View File

@ -1,38 +1,48 @@
#include "svg_renderer.hpp"
#include "math/constants.hpp"
#include <fstream>
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 << "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" << width << "\" height=\"" << height
<< "\" viewBox=\"0 0 " << width << " " << height << "\" role=\"img\" aria-label=\"KIV/CPP\">" << std::endl;
// white bg
m_out << " <rect width=\"100%\" height=\"100%\" fill=\"#FFFFFF\" />" << std::endl;
}
void SvgRenderer::DrawLine(const math::Vector& p0, const math::Vector& p1)
{
m_file << " <line x1=\"" << p0.x << "\" y1=\"" << p0.y << "\" x2=\"" << p1.x << "\" y2=\"" << p1.y << "\" "
<< "stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"round\" />" << std::endl;
m_out << " <line x1=\"" << p0.x << "\" y1=\"" << p0.y << "\" x2=\"" << p1.x << "\" y2=\"" << p1.y << "\" "
<< "stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"round\" />" << std::endl;
}
void SvgRenderer::DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle)
{
float angleDeg = angle * math::RAD_TO_DEG;
m_file << " <rect x=\"" << pos.x << "\" y=\"" << pos.y << "\" width=\"" << size.x << "\" height=\"" << size.y
<< "\" fill=\"none\" stroke=\"#000000\" stroke-width=\"2\" stroke-linejoin=\"round\" transform=\"rotate("
<< angleDeg << ", " << pos.x << ", " << pos.y << ")\" />" << std::endl;
m_out << " <rect x=\"" << pos.x << "\" y=\"" << pos.y << "\" width=\"" << size.x << "\" height=\"" << size.y
<< "\" fill=\"none\" stroke=\"#000000\" stroke-width=\"2\" stroke-linejoin=\"round\" transform=\"rotate("
<< angleDeg << ", " << pos.x << ", " << pos.y << ")\" />" << std::endl;
}
void SvgRenderer::DrawCircle(const math::Vector& center, float radius)
{
m_file << " <circle cx=\"" << center.x << "\" cy=\"" << center.y << "\" r=\"" << radius
<< "\" fill=\"none\" stroke=\"#000000\" stroke-width=\"2\" />" << std::endl;
m_out << " <circle cx=\"" << center.x << "\" cy=\"" << center.y << "\" r=\"" << radius
<< "\" fill=\"none\" stroke=\"#000000\" stroke-width=\"2\" />" << std::endl;
}
SvgRenderer::~SvgRenderer()
void SvgRenderer::Save(const std::filesystem::path& path)
{
m_file << "</svg>" << std::endl;
std::ofstream file{path};
if (!file.is_open())
{
throw std::runtime_error{"Cannot open file for writing: " + path.string()};
}
file << "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" << m_width << "\" height=\"" << m_height
<< "\" viewBox=\"0 0 " << m_width << " " << m_height << "\" role=\"img\" aria-label=\"KIV/CPP\">" << std::endl;
file << m_out.str();
file << "</svg>" << std::endl;
}

View File

@ -3,19 +3,27 @@
#include "renderer.hpp"
#include <filesystem>
#include <fstream>
#include <sstream>
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;
};

View File

@ -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;
};

View File

@ -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 <class TShape, class... TArgs> void AddShape(TArgs&&... args)
template <typename TShape, typename... TArgs>
requires std::derived_from<TShape, Shape>
void AddShape(TArgs&&... args)
{
m_shapes.emplace_back(std::make_unique<TShape>(std::forward<TArgs>(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 <auto Fun, typename TSelf, typename... TArgs>
void ShapesCall(this TSelf&& self, TArgs&&... args)
{
for (auto& shape : self.m_shapes)
(shape.get()->*Fun)(std::forward<TArgs>(args)...);
}
private:
std::vector<std::unique_ptr<Shape>> m_shapes;
template <auto TFun, class... TArgs> void ShapesCall(TArgs&&... args)
{
for (auto& shape : m_shapes)
{
(shape.get()->*TFun)(std::forward<TArgs>(args)...);
}
}
template <auto TFun, class... TArgs> void ShapesCall(TArgs&&... args) const
{
for (const auto& shape : m_shapes)
{
(shape.get()->*TFun)(std::forward<TArgs>(args)...);
}
}
};
} // namespace shapes

View File

@ -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;
};

View File

@ -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;

View File

@ -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: