SVG + cmd line parsing
This commit is contained in:
parent
9b517b6aee
commit
4f2819bd81
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
.cache/
|
||||
.vscode/
|
||||
build/
|
||||
*.txt
|
||||
*.svg
|
||||
*.pgm
|
||||
|
||||
@ -7,8 +7,8 @@ set(CMAKE_CXX_STANDARD 20)
|
||||
add_executable(drawing
|
||||
"math/transforms.cpp"
|
||||
"renderers/pgm_renderer.cpp"
|
||||
"renderers/svg_renderer.cpp"
|
||||
"shapes/circle.cpp"
|
||||
"shapes/group.cpp"
|
||||
"shapes/line.cpp"
|
||||
"shapes/rectangle.cpp"
|
||||
"input_file.cpp"
|
||||
|
||||
@ -9,7 +9,9 @@
|
||||
#include "shapes/line.hpp"
|
||||
#include "shapes/rectangle.hpp"
|
||||
|
||||
InputFile::InputFile(const std::filesystem::path& path) : m_file(path)
|
||||
#include "math/constants.hpp"
|
||||
|
||||
InputFile::InputFile(const std::filesystem::path& path) : m_file(path), m_cmdsProcessed(0)
|
||||
{
|
||||
if (m_file.bad())
|
||||
{
|
||||
@ -72,11 +74,6 @@ static void RegisterCommand(CommandMap& cmdMap, const std::string& cmd, THandler
|
||||
};
|
||||
}
|
||||
|
||||
static float DegToRad(float deg)
|
||||
{
|
||||
return deg * 0.0174532925f;
|
||||
}
|
||||
|
||||
shapes::Group InputFile::Parse()
|
||||
{
|
||||
shapes::Group shapes;
|
||||
@ -108,7 +105,7 @@ 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}, DegToRad(deg)); });
|
||||
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())
|
||||
@ -121,6 +118,7 @@ shapes::Group InputFile::Parse()
|
||||
|
||||
while (std::getline(m_file, line))
|
||||
{
|
||||
// TODO: find # mid line
|
||||
if (line.empty() || line[0] == '#')
|
||||
continue;
|
||||
|
||||
@ -133,6 +131,7 @@ shapes::Group InputFile::Parse()
|
||||
throw std::runtime_error("Unknown command: " + cmdName);
|
||||
|
||||
cmd->second(iss);
|
||||
++m_cmdsProcessed;
|
||||
}
|
||||
|
||||
return shapes;
|
||||
|
||||
@ -14,6 +14,12 @@ class InputFile
|
||||
|
||||
shapes::Group Parse();
|
||||
|
||||
size_t GetNumProcessedCmds() const
|
||||
{
|
||||
return m_cmdsProcessed;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ifstream m_file;
|
||||
size_t m_cmdsProcessed;
|
||||
};
|
||||
|
||||
76
main.cpp
76
main.cpp
@ -1,29 +1,83 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
|
||||
#include "input_file.hpp"
|
||||
#include "renderers/pgm_renderer.hpp"
|
||||
#include "renderers/svg_renderer.hpp"
|
||||
|
||||
int main()
|
||||
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);
|
||||
shapes.Draw(renderer);
|
||||
}
|
||||
|
||||
static size_t ParseDim(const char* start, const char* end)
|
||||
{
|
||||
size_t val;
|
||||
auto [ptr, ec] = std::from_chars(start, end, val);
|
||||
|
||||
if (ec != std::errc())
|
||||
{
|
||||
throw std::runtime_error("Cannot parse size");
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
return std::make_tuple(width, height);
|
||||
}
|
||||
|
||||
static void Run(const std::string& inputFile, const std::string& outputFile, const std::string& sizeStr)
|
||||
{
|
||||
try
|
||||
{
|
||||
InputFile file("test.txt");
|
||||
auto [width, height] = ParseSize(sizeStr);
|
||||
|
||||
std::ofstream("here.txt");
|
||||
InputFile file(inputFile);
|
||||
shapes::Group shapes = file.Parse();
|
||||
|
||||
auto shapes = file.Parse();
|
||||
|
||||
PgmRenderer renderer("output.pgm", 300, 300);
|
||||
shapes.Draw(renderer);
|
||||
|
||||
renderer.Flush();
|
||||
|
||||
std::cout << "sjeta" << std::endl;
|
||||
if (outputFile.ends_with(".svg"))
|
||||
Render<SvgRenderer>(shapes, outputFile, width, height);
|
||||
else
|
||||
Render<PgmRenderer>(shapes, outputFile, width, height);
|
||||
|
||||
std::cout << "OK" << std::endl;
|
||||
std::cout << file.GetNumProcessedCmds() << std::endl;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 4)
|
||||
{
|
||||
const char* command = "drawing";
|
||||
|
||||
if (argc > 0)
|
||||
command = argv[0];
|
||||
|
||||
std::cerr << "Usage: " << command << " <input_file> <output_file> <size>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
Run(argv[1], argv[2], argv[3]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
10
math/constants.hpp
Normal file
10
math/constants.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace math
|
||||
{
|
||||
|
||||
constexpr float PI = 3.14159265358979323846f;
|
||||
constexpr float DEG_TO_RAD = PI / 180.0f;
|
||||
constexpr float RAD_TO_DEG = 1.0f / DEG_TO_RAD;
|
||||
|
||||
} // namespace math
|
||||
@ -1,5 +1,6 @@
|
||||
#include "transforms.hpp"
|
||||
#include <cmath>
|
||||
#include "constants.hpp"
|
||||
|
||||
math::Vector math::RotatePoint(const Vector& center, float angle, const Vector& p)
|
||||
{
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
namespace math
|
||||
{
|
||||
|
||||
constexpr float PI = 3.14159265358979323846f;
|
||||
|
||||
Vector RotatePoint(const Vector& center, float angle, const Vector& p);
|
||||
Vector ScalePoint(const Vector& center, float factor, const Vector& p);
|
||||
float RotateAngle(float originalAngle, float rotationAngle);
|
||||
|
||||
@ -6,9 +6,32 @@
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
struct Color
|
||||
class Color
|
||||
{
|
||||
uint8_t l{};
|
||||
public:
|
||||
Color() : l{0}
|
||||
{
|
||||
}
|
||||
|
||||
Color(uint8_t l) : l{l}
|
||||
{
|
||||
}
|
||||
|
||||
uint8_t l{}; // luminence
|
||||
|
||||
void Blend(const Color& src, uint8_t alpha)
|
||||
{
|
||||
l = BlendChannel(l, src.l, alpha);
|
||||
// l = alpha;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
class Bitmap
|
||||
@ -43,12 +66,21 @@ class Bitmap
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_width, m_height;
|
||||
std::vector<Color> m_data;
|
||||
|
||||
@ -37,9 +37,15 @@ void PgmRenderer::DrawRectangle(const math::Vector& pos, const math::Vector& siz
|
||||
|
||||
void PgmRenderer::DrawCircle(const math::Vector& center, float radius)
|
||||
{
|
||||
// TODO: cast
|
||||
RasterizeCircle(center.x, center.y, radius);
|
||||
}
|
||||
|
||||
PgmRenderer::~PgmRenderer()
|
||||
{
|
||||
Flush();
|
||||
}
|
||||
|
||||
void PgmRenderer::Flush()
|
||||
{
|
||||
m_file << "P2" << std::endl;
|
||||
@ -60,47 +66,57 @@ void PgmRenderer::Flush()
|
||||
|
||||
void PgmRenderer::RasterizeLine(int x0, int y0, int x1, int y1)
|
||||
{
|
||||
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
|
||||
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
|
||||
int err = dx + dy, e2; /* error value e_xy */
|
||||
// TODO: fix
|
||||
const Color lineColor{0x00};
|
||||
float w = 2.0f;
|
||||
|
||||
for (;;)
|
||||
{ /* loop */
|
||||
m_bitmap[y0][x0].l = 0x00;
|
||||
if (x0 == x1 && y0 == y1)
|
||||
break;
|
||||
e2 = 2 * err;
|
||||
if (e2 >= dy)
|
||||
{
|
||||
err += dy;
|
||||
x0 += sx;
|
||||
} /* e_xy+e_x > 0 */
|
||||
if (e2 <= dx)
|
||||
{
|
||||
err += dx;
|
||||
y0 += sy;
|
||||
} /* e_xy+e_y < 0 */
|
||||
int dx = abs(x1 - x0);
|
||||
int dy = abs(y1 - y0);
|
||||
int sx = (x0 < x1) ? 1 : -1;
|
||||
int sy = (y0 < y1) ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
// perpendicular offsets for thickness
|
||||
int ox = 0, oy = 0;
|
||||
if (dx > dy) {
|
||||
oy = 1;
|
||||
} else {
|
||||
ox = 1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
// draw a square of pixels for thickness
|
||||
for (int i = -w/2; i <= w/2; i++) {
|
||||
for (int j = -w/2; j <= w/2; j++) {
|
||||
m_bitmap.Put(x0 + i*ox, y0 + j*oy, lineColor, 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; x0 += sx; }
|
||||
if (e2 < dx) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
void PgmRenderer::RasterizeCircle(int xm, int ym, int r)
|
||||
void PgmRenderer::RasterizeCircle(int cx, int cy, int r)
|
||||
{
|
||||
int x = -r, y = 0, err = 2 - 2 * r; /* II. Quadrant */
|
||||
do
|
||||
{
|
||||
m_bitmap[ym + y][xm - x].l = 0x00;
|
||||
m_bitmap[ym - x][xm - y].l = 0x00;
|
||||
m_bitmap[ym - y][xm + x].l = 0x00;
|
||||
m_bitmap[ym + x][xm + y].l = 0x00;
|
||||
// TODO: fix
|
||||
|
||||
// setPixel(xm - x, ym + y); /* I. Quadrant */
|
||||
// setPixel(xm - y, ym - x); /* II. Quadrant */
|
||||
// setPixel(xm + x, ym - y); /* III. Quadrant */
|
||||
// setPixel(xm + y, ym + x); /* IV. Quadrant */
|
||||
r = err;
|
||||
if (r <= y)
|
||||
err += ++y * 2 + 1; /* e_xy+e_y < 0 */
|
||||
if (r > x || err > y)
|
||||
err += ++x * 2 + 1; /* e_xy+e_x > 0 or no 2nd y-step */
|
||||
} while (x < 0);
|
||||
const Color circleColor{0x00};
|
||||
float w = 2.0f;
|
||||
|
||||
|
||||
int r_outer = r + w / 2;
|
||||
int r_inner = r - w / 2;
|
||||
if (r_inner < 0) r_inner = 0;
|
||||
|
||||
for (int y = -r_outer; y <= r_outer; y++) {
|
||||
for (int x = -r_outer; x <= r_outer; x++) {
|
||||
int dist2 = x*x + y*y;
|
||||
if (dist2 <= r_outer*r_outer && dist2 >= r_inner*r_inner) {
|
||||
m_bitmap.Put(cx + x, cy + y, circleColor, 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,9 +16,7 @@ class PgmRenderer : public Renderer
|
||||
void DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) override;
|
||||
void DrawCircle(const math::Vector& center, float radius) override;
|
||||
|
||||
void Flush() override;
|
||||
|
||||
~PgmRenderer() override = default;
|
||||
~PgmRenderer() override;
|
||||
|
||||
private:
|
||||
std::ofstream m_file;
|
||||
@ -26,4 +24,6 @@ class PgmRenderer : public Renderer
|
||||
|
||||
void RasterizeLine(int x0, int y0, int x1, int y1);
|
||||
void RasterizeCircle(int xm, int ym, int r);
|
||||
|
||||
void Flush();
|
||||
};
|
||||
@ -8,7 +8,9 @@ class Renderer
|
||||
virtual void DrawRectangle(const math::Vector& pos, const math::Vector& size, float angle) = 0;
|
||||
virtual void DrawCircle(const math::Vector& center, float radius) = 0;
|
||||
|
||||
virtual void Flush() = 0;
|
||||
virtual void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~Renderer() = default;
|
||||
};
|
||||
|
||||
38
renderers/svg_renderer.cpp
Normal file
38
renderers/svg_renderer.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "svg_renderer.hpp"
|
||||
|
||||
#include "math/constants.hpp"
|
||||
|
||||
SvgRenderer::SvgRenderer(const std::filesystem::path& path, size_t width, size_t height) : m_file(path)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
SvgRenderer::~SvgRenderer()
|
||||
{
|
||||
m_file << "</svg>" << std::endl;
|
||||
}
|
||||
21
renderers/svg_renderer.hpp
Normal file
21
renderers/svg_renderer.hpp
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "renderer.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
class SvgRenderer : public Renderer
|
||||
{
|
||||
public:
|
||||
SvgRenderer(const std::filesystem::path& path, 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;
|
||||
|
||||
private:
|
||||
std::ofstream m_file;
|
||||
};
|
||||
@ -18,7 +18,7 @@ void shapes::Circle::Scale(const math::Vector& center, float factor)
|
||||
m_radius *= factor;
|
||||
}
|
||||
|
||||
void shapes::Circle::Draw(Renderer& renderer)
|
||||
void shapes::Circle::Draw(Renderer& renderer) const
|
||||
{
|
||||
renderer.DrawCircle(m_center, m_radius);
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ class Circle : public Shape
|
||||
void Rotate(const math::Vector& center, float angle) override;
|
||||
void Scale(const math::Vector& center, float factor) override;
|
||||
|
||||
void Draw(Renderer& renderer) override;
|
||||
void Draw(Renderer& renderer) const override;
|
||||
|
||||
~Circle() override = default;
|
||||
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
#include "group.hpp"
|
||||
|
||||
// void shapes::Group::Translate(const math::Vector& offset)
|
||||
// {
|
||||
// for (const auto& shape : m_shapes)
|
||||
// {
|
||||
// shape->Translate(offset);
|
||||
// }
|
||||
// }
|
||||
|
||||
// void shapes::Group::Rotate(const math::Vector& center, float angle)
|
||||
// {
|
||||
// for (const auto& shape : m_shapes)
|
||||
// {
|
||||
// shape->Rotate(center, angle);
|
||||
// }
|
||||
// }
|
||||
|
||||
// void shapes::Group::Scale(const math::Vector& center, float factor)
|
||||
// {
|
||||
// for (const auto& shape : m_shapes)
|
||||
// {
|
||||
// shape->Scale(center, factor);
|
||||
// }
|
||||
// }
|
||||
|
||||
// void shapes::Group::Draw(Renderer& renderer)
|
||||
// {
|
||||
// for (const auto& shape : m_shapes)
|
||||
// {
|
||||
// shape->Draw(renderer);
|
||||
// }
|
||||
// }
|
||||
@ -13,12 +13,13 @@ class Group : public Shape
|
||||
public:
|
||||
Group() = default;
|
||||
|
||||
Group(Group&& other)
|
||||
Group(Group&& other) : m_shapes{std::move(other.m_shapes)}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Group& operator=(Group&& other)
|
||||
{
|
||||
// TODO: overit
|
||||
m_shapes = std::move(other.m_shapes);
|
||||
return *this;
|
||||
}
|
||||
@ -43,7 +44,7 @@ class Group : public Shape
|
||||
ShapesCall<&Shape::Scale>(center, factor);
|
||||
}
|
||||
|
||||
void Draw(Renderer& renderer) override
|
||||
void Draw(Renderer& renderer) const override
|
||||
{
|
||||
ShapesCall<&Shape::Draw>(renderer);
|
||||
}
|
||||
@ -60,6 +61,14 @@ class Group : public Shape
|
||||
(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
|
||||
@ -20,7 +20,7 @@ void shapes::Line::Scale(const math::Vector& center, float factor)
|
||||
m_p1 = math::ScalePoint(center, factor, m_p1);
|
||||
}
|
||||
|
||||
void shapes::Line::Draw(Renderer& renderer)
|
||||
void shapes::Line::Draw(Renderer& renderer) const
|
||||
{
|
||||
renderer.DrawLine(m_p0, m_p1);
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ class Line : public Shape
|
||||
void Rotate(const math::Vector& center, float angle) override;
|
||||
void Scale(const math::Vector& center, float factor) override;
|
||||
|
||||
void Draw(Renderer& renderer) override;
|
||||
void Draw(Renderer& renderer) const override;
|
||||
|
||||
private:
|
||||
math::Vector m_p0;
|
||||
|
||||
@ -19,7 +19,7 @@ void shapes::Rectangle::Scale(const math::Vector& center, float factor)
|
||||
m_size *= factor;
|
||||
}
|
||||
|
||||
void shapes::Rectangle::Draw(Renderer& renderer)
|
||||
void shapes::Rectangle::Draw(Renderer& renderer) const
|
||||
{
|
||||
renderer.DrawRectangle(m_pos, m_size, m_angle);
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ class Rectangle : public Shape
|
||||
void Rotate(const math::Vector& center, float angle) override;
|
||||
void Scale(const math::Vector& center, float factor) override;
|
||||
|
||||
void Draw(Renderer& renderer) override;
|
||||
void Draw(Renderer& renderer) const override;
|
||||
|
||||
~Rectangle() override = default;
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ class Shape
|
||||
virtual void Rotate(const math::Vector& center, float angle) = 0;
|
||||
virtual void Scale(const math::Vector& center, float factor) = 0;
|
||||
|
||||
virtual void Draw(Renderer& renderer) = 0;
|
||||
virtual void Draw(Renderer& renderer) const = 0;
|
||||
|
||||
virtual ~Shape() = default;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user