Add comments, change renderers to have Save instead of saving on destruction
This commit is contained in:
parent
e5bf048d21
commit
895cddc739
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,14 +142,24 @@ shapes::Group InputFile::Parse()
|
||||
shapes.Scale(math::Vector{cx, cy}, factor);
|
||||
});
|
||||
|
||||
std::string line;
|
||||
/* ---------------------------- */
|
||||
|
||||
std::string line;
|
||||
size_t lineNum = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (std::getline(m_file, line))
|
||||
{
|
||||
// TODO: find # mid line
|
||||
++lineNum;
|
||||
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
auto commentPos = line.find('#');
|
||||
if (commentPos != std::string::npos)
|
||||
line = line.substr(0, commentPos);
|
||||
|
||||
std::istringstream iss(line);
|
||||
std::string cmdName = ReadVal<std::string>(iss);
|
||||
|
||||
@ -133,6 +171,11 @@ shapes::Group InputFile::Parse()
|
||||
cmd->second(iss);
|
||||
++m_cmdsProcessed;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw std::runtime_error(std::format("Line {}: {}", lineNum, e.what()));
|
||||
}
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
@ -5,6 +5,11 @@
|
||||
|
||||
#include "shapes/group.hpp"
|
||||
|
||||
/**
|
||||
* @brief Input file parser
|
||||
*
|
||||
* Parses an input file containing drawing commands and constructs a `Group` of shapes.
|
||||
*/
|
||||
class InputFile
|
||||
{
|
||||
public:
|
||||
@ -12,10 +17,7 @@ class InputFile
|
||||
|
||||
shapes::Group Parse();
|
||||
|
||||
size_t GetNumProcessedCmds() const
|
||||
{
|
||||
return m_cmdsProcessed;
|
||||
}
|
||||
size_t GetNumProcessedCmds() const { return m_cmdsProcessed; }
|
||||
|
||||
private:
|
||||
std::ifstream m_file;
|
||||
|
||||
33
main.cpp
33
main.cpp
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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
|
||||
@ -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}
|
||||
{
|
||||
}
|
||||
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)
|
||||
{
|
||||
|
||||
@ -7,13 +7,9 @@
|
||||
class Color
|
||||
{
|
||||
public:
|
||||
Color() : l{0}
|
||||
{
|
||||
}
|
||||
Color() : l{0} {}
|
||||
|
||||
Color(uint8_t l) : l{l}
|
||||
{
|
||||
}
|
||||
Color(uint8_t l) : l{l} {}
|
||||
|
||||
uint8_t l{}; // luminence
|
||||
|
||||
@ -26,8 +22,8 @@ class Color
|
||||
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)) /
|
||||
return static_cast<uint8_t>(
|
||||
(static_cast<int>(a) * (255 - static_cast<int>(alpha)) + static_cast<int>(b) * static_cast<int>(alpha)) /
|
||||
255);
|
||||
}
|
||||
};
|
||||
@ -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,7 +60,6 @@ class Bitmap
|
||||
return; // out of bounds
|
||||
|
||||
(*this)[y][x].Blend(color, alpha);
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@ -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};
|
||||
|
||||
if (!file.is_open())
|
||||
{
|
||||
throw std::runtime_error{"Cannot open file for writing: " + path.string()};
|
||||
}
|
||||
|
||||
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;
|
||||
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++)
|
||||
|
||||
@ -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);
|
||||
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);
|
||||
|
||||
~PgmRenderer() override = default;
|
||||
|
||||
private:
|
||||
std::ofstream m_file;
|
||||
Bitmap m_bitmap;
|
||||
|
||||
void RasterizeLine(int x0, int y0, int x1, int y1);
|
||||
void RasterizeCircle(int xm, int ym, int r);
|
||||
|
||||
void Flush();
|
||||
};
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
#pragma once
|
||||
#include "math/vector.hpp"
|
||||
|
||||
/**
|
||||
* @brief Renderer interface
|
||||
*
|
||||
* Represents an inteface for different renderers that can draw basic shapes.
|
||||
*/
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
#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 << "\" "
|
||||
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;
|
||||
}
|
||||
|
||||
@ -21,18 +19,30 @@ void SvgRenderer::DrawRectangle(const math::Vector& pos, const math::Vector& siz
|
||||
{
|
||||
float angleDeg = angle * math::RAD_TO_DEG;
|
||||
|
||||
m_file << " <rect x=\"" << pos.x << "\" y=\"" << pos.y << "\" width=\"" << size.x << "\" height=\"" << size.y
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
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);
|
||||
|
||||
~SvgRenderer() override = default;
|
||||
|
||||
private:
|
||||
std::ofstream m_file;
|
||||
size_t m_width, m_height;
|
||||
std::ostringstream m_out;
|
||||
};
|
||||
@ -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)
|
||||
{
|
||||
}
|
||||
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;
|
||||
|
||||
@ -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:
|
||||
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:
|
||||
// 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
|
||||
@ -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)
|
||||
{
|
||||
}
|
||||
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;
|
||||
|
||||
@ -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)
|
||||
{
|
||||
}
|
||||
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;
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user