diff --git a/CMakeLists.txt b/CMakeLists.txt index 55e2e15..d4e2c35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,6 @@ add_executable( src/painter.cpp src/painter.hpp src/main.cpp - + src/shaders.hpp src/shaders.cpp ) \ No newline at end of file diff --git a/src/drawing_assets.cpp b/src/drawing_assets.cpp index 42f7551..2e0afb3 100644 --- a/src/drawing_assets.cpp +++ b/src/drawing_assets.cpp @@ -61,4 +61,79 @@ void draw_line(Frame *frame, int x1, int y1, int x2, int y2, COLOR color) { frame->set_pixel(x, y, color); } } -} \ No newline at end of file +} + +void fill_triangle(Frame *f, int x0, int y0, int x1, int y1, int x2, int y2, Shader const *shader) { +// Отсортируем точки таким образом, чтобы выполнилось условие: y0 < y1 < y2 + if (y1 < y0) { + std::swap(y1, y0); + std::swap(x1, x0); + } + if (y2 < y1) { + std::swap(y2, y1); + std::swap(x2, x1); + } + if (y1 < y0) { + std::swap(y1, y0); + std::swap(x1, x0); + } +// Определяем номера строк пикселей, в которых располагаются точки треугольника + int Y0 = (int) (y0 + 0.5f); + int Y1 = (int) (y1 + 0.5f); + int Y2 = (int) (y2 + 0.5f); +// Отсечение невидимой части треугольника + if (Y0 < 0) Y0 = 0; + else if (Y0 >= f->height) Y0 = f->height; + if (Y1 < 0) Y1 = 0; + else if (Y1 >= f->height) Y1 = f->height; + if (Y2 < 0) Y2 = 0; + else if (Y2 >= f->height) Y2 = f->height; +// Рисование верхней части треугольника + for (float y = Y0 + 0.5f; y < Y1; y++) { + int X0 = (int) ((y - y0) / (y1 - y0) * (x1 - x0) + x0 + 0.5f); + int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f); + if (X0 > X1) std::swap(X0, X1); + if (X0 < 0) X0 = 0; + if (X1 > f->width) X1 = f->width; + for (int x = X0; x < X1; x++) { + f->set_pixel(x, y, shader->get_color(x + 0.5f, y)); + } + } +// Рисование нижней части треугольника + for (float y = Y1 + 0.5f; y < Y2; y++) { + int X0 = (int) ((y - y1) / (y2 - y1) * (x2 - x1) + x1 + 0.5f); + int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f); + if (X0 > X1) std::swap(X0, X1); + if (X0 < 0) X0 = 0; + if (X1 > f->width) X1 = f->width; + for (int x = X0; x < X1; x++) { + f->set_pixel(x, y, shader->get_color(x + 0.5f, y)); + } + } +} + + + +COLOR ColorFromHSV(double hue, double saturation, double value) { + int hi = int(floor(hue / 60)) % 6; + double f = hue / 60 - floor(hue / 60); + value = value * 255; + const auto v = (unsigned char) (value); + const auto p = (unsigned char) (value * (1 - saturation)); + const auto q = (unsigned char) (value * (1 - f * saturation)); + const auto t = (unsigned char) (value * (1 - (1 - f) * saturation)); + switch (hi) { + case 0: + return COLOR{v, t, p}; + case 1: + return COLOR{q, v, p}; + case 2: + return COLOR{p, v, t}; + case 3: + return COLOR{p, q, v}; + case 4: + return COLOR{t, p, v}; + default: + return COLOR{v, p, q}; + } +} diff --git a/src/drawing_assets.hpp b/src/drawing_assets.hpp index a809a36..6d6bfe4 100644 --- a/src/drawing_assets.hpp +++ b/src/drawing_assets.hpp @@ -114,4 +114,66 @@ void draw_polyline(Frame *frame, COLOR color, point_t ...points) { draw_polyline_::draw(frame, color, points...); draw_line(frame, draw_polyline_::last(points...), draw_polyline_::first(points...), color); } -} \ No newline at end of file +} + +class Shader { +public: + [[nodiscard]] virtual COLOR get_color(int x, int y) const = 0; +}; + +class MonoShader : public Shader { +private: + COLOR color; +public: + explicit MonoShader(COLOR color) : color{color} {} + + [[nodiscard]] COLOR get_color(int x, int y) const override { + return this->color; + } +}; + +class BarycentricInterpolatorShader : public Shader { +private: + int x0, y0, x1, y1, x2, y2, S; + COLOR C0, C1, C2; +public: + BarycentricInterpolatorShader(float _x0, float _y0, float _x1, float _y1, float + _x2, float _y2, COLOR A0, COLOR A1, COLOR A2) : + x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2), + S((_y1 - _y2) * (_x0 - _x2) + (_x2 - _x1) * (_y0 - _y2)), C0(A0), C1(A1), + C2(A2) { + } + + COLOR get_color(int x, int y) const override { +// Барицентрическая интерполяция + float h0 = ((y1 - y2) * (x - x2) + (x2 - x1) * (y - y2)) / S; + float h1 = ((y2 - y0) * (x - x2) + (x0 - x2) * (y - y2)) / S; + float h2 = 1 - h0 - h1; + float r = h0 * C0.red + h1 * C1.red + h2 * C2.red; + float g = h0 * C0.green + h1 * C1.green + h2 * C2.green; + float b = h0 * C0.blue + h1 * C1.blue + h2 * C2.blue; + float a = h0 * C0.alpha + h1 * C1.alpha + h2 * C2.alpha; + return COLOR(r, g, b, a); + } +}; + +void fill_triangle(Frame *f, int x0, int y0, int x1, int y1, int x2, int y2, Shader const *shader); + + +inline void fill_triangle(Frame *f, Point p0, Point p1, Point p2, Shader const *shader) { + fill_triangle(f, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, shader); +} + +inline void fill_triangle(Frame *f, int x0, int y0, int x1, int y1, int x2, int y2, COLOR color) { + MonoShader s{color}; + fill_triangle(f, x0, y0, x1, y1, x2, y2, &s); + +} + + +inline void fill_triangle(Frame *f, Point p0, Point p1, Point p2, COLOR color) { + MonoShader s{color}; + fill_triangle(f, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, &s); +} + +COLOR ColorFromHSV(double hue, double saturation, double value); \ No newline at end of file diff --git a/src/painter.cpp b/src/painter.cpp index c019675..13ed2a1 100644 --- a/src/painter.cpp +++ b/src/painter.cpp @@ -35,8 +35,42 @@ public: } }; +class Variant3S1Painter : public Painter { +public: + void draw(PainterState *state, Frame *frame) const override { + int W = frame->width, H = frame->height; + // Размер рисунка возьмём меньше (7 / 8), чтобы он не касался границ экрана + float a = 7.0f / 8 * ((W < H) ? W - 1 : H - 1); + if (a < 1) return; // Если окно очень маленькое, то ничего не рисуем + float angle = state->angle; // Угол поворота + a = a / 2; + + Point center{W / 2, H / 2}; + + + + // Рисуем описанную окружность + draw_circle(frame, center, a, COLOR(0, 0, 0)); + draw_polyline( + frame, COLOR{0, 200, 0}, + center.moved(0, a).rotated_around(center, 0 + angle), + center.moved(0, a).rotated_around(center, +2.0944 + angle), + center.moved(0, a).rotated_around(center, -2.0944 + angle) + ); + draw_circle(frame, center, a / 2, COLOR(0, 200, 0)); + draw_circle(frame, center.moved(0, a *2 / 3).rotated_around(center, angle), a / 6, COLOR(0, 200, 0)); + draw_circle(frame, center.moved(0, a *2 / 3).rotated_around(center, +2.0944 + angle), a / 6, COLOR(0, 200, 0)); + draw_circle(frame, center.moved(0, a *2 / 3).rotated_around(center, -2.0944 + angle), a / 6, COLOR(0, 200, 0)); + + // Рисуем пиксель, на который кликнул пользователь + if (state->clicked_pixel.x >= 0 && state->clicked_pixel.x < W && + state->clicked_pixel.y >= 0 && state->clicked_pixel.y < H) + frame->set_pixel(state->clicked_pixel.x, state->clicked_pixel.y, {34, 175, 60}); // Пиксель зелёного цвета + } +}; + static auto predefined_painters_ = std::make_tuple( - DemoPainter() + Variant3S1Painter() ); Painter const *const predefined_painters[] = { diff --git a/src/shaders.cpp b/src/shaders.cpp index 3ce86ae..a08ae31 100644 --- a/src/shaders.cpp +++ b/src/shaders.cpp @@ -1,127 +1,26 @@ -#include #include "frame.hpp" -#include "painter.hpp" +#include "shaders.hpp" -template -void Triangle(Frame *f, float x0, float y0, float x1, float y1, float x2, float y2, COLOR color, ShaderClass *shader) { -// Отсортируем точки таким образом, чтобы выполнилось условие: y0 < y1 < y2 - if (y1 < y0) { - std::swap(y1, y0); - std::swap(x1, x0); - } - if (y2 < y1) { - std::swap(y2, y1); - std::swap(x2, x1); - } - if (y1 < y0) { - std::swap(y1, y0); - std::swap(x1, x0); - } -// Определяем номера строк пикселей, в которых располагаются точки треугольника - int Y0 = (int) (y0 + 0.5f); - int Y1 = (int) (y1 + 0.5f); - int Y2 = (int) (y2 + 0.5f); -// Отсечение невидимой части треугольника - if (Y0 < 0) Y0 = 0; - else if (Y0 >= f->height) Y0 = f->height; - if (Y1 < 0) Y1 = 0; - else if (Y1 >= f->height) Y1 = f->height; - if (Y2 < 0) Y2 = 0; - else if (Y2 >= f->height) Y2 = f->height; -// Рисование верхней части треугольника - for (float y = Y0 + 0.5f; y < Y1; y++) { - int X0 = (int) ((y - y0) / (y1 - y0) * (x1 - x0) + x0 + 0.5f); - int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f); - if (X0 > X1) std::swap(X0, X1); - if (X0 < 0) X0 = 0; - if (X1 > f->width) X1 = f->width; - for (int x = X0; x < X1; x++) { -// f(x + 0.5, y) - SetPixel(x, y, shader->getColor(x + 0.5f, y)); - } - } -// Рисование нижней части треугольника - for (float y = Y1 + 0.5f; y < Y2; y++) { - int X0 = (int) ((y - y1) / (y2 - y1) * (x2 - x1) + x1 + 0.5f); - int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f); - if (X0 > X1) std::swap(X0, X1); - if (X0 < 0) X0 = 0; - if (X1 > f->width) X1 = f->width; - for (int x = X0; x < X1; x++) { -// f(x + 0.5, y) - SetPixel(x, y, shader->getColor(x + 0.5f, y)); - } - } -} - -class BarycentricInterpolator { - float x0, y0, x1, y1, x2, y2, S; - COLOR C0, C1, C2; -public: - BarycentricInterpolator(float _x0, float _y0, float _x1, float _y1, float - _x2, float _y2, COLOR A0, COLOR A1, COLOR A2) : - x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2), - S((_y1 - _y2) * (_x0 - _x2) + (_x2 - _x1) * (_y0 - _y2)), C0(A0), C1(A1), - C2(A2) { - } - - COLOR getColor(float x, float y) { +COLOR BarycentricInterpolator::get_color(int x, int y) const { // Барицентрическая интерполяция - float h0 = ((y1 - y2) * (x - x2) + (x2 - x1) * (y - y2)) / S; - float h1 = ((y2 - y0) * (x - x2) + (x0 - x2) * (y - y2)) / S; - float h2 = 1 - h0 - h1; - float r = h0 * C0.red + h1 * C1.red + h2 * C2.red; - float g = h0 * C0.green + h1 * C1.green + h2 * C2.green; - float b = h0 * C0.blue + h1 * C1.blue + h2 * C2.blue; - float a = h0 * C0.alpha + h1 * C1.alpha + h2 * C2.alpha; - return COLOR(r, g, b, a); - } -}; - - -class RadialBrush { - float cx, cy; // Центр прямоугольника - COLOR C0, C1; // Цвета радиальной заливки - float angle; // Начальный угол заливки -public: - RadialBrush(float _x0, float _y0, float _x1, float _y1, COLOR A0, COLOR A1, float - _angle) : - cx((_x0 + _x1) / 2.0f), cy((_y0 + _y1) / 2.0f), - C0(A0), C1(A1), angle(_angle) { - } - - COLOR getColor(float x, float y) { - double dx = (double) x - cx, dy = (double) y - cy; - double radius = sqrt(dx * dx + dy * dy); - float h0 = (sin(radius / 10 + angle) + 1.0f) / 2; - float h1 = 1 - h0; - float r = h0 * C0.red + h1 * C1.red; - float g = h0 * C0.green + h1 * C1.green; - float b = h0 * C0.blue + h1 * C1.blue; - return COLOR(r, g, b); - } -}; - -COLOR ColorFromHSV(double hue, double saturation, double value) { - int hi = int(floor(hue / 60)) % 6; - double f = hue / 60 - floor(hue / 60); - value = value * 255; - const auto v = (unsigned char) (value); - const auto p = (unsigned char) (value * (1 - saturation)); - const auto q = (unsigned char) (value * (1 - f * saturation)); - const auto t = (unsigned char) (value * (1 - (1 - f) * saturation)); - switch (hi) { - case 0: - return COLOR{v, t, p}; - case 1: - return COLOR{q, v, p}; - case 2: - return COLOR{p, v, t}; - case 3: - return COLOR{p, q, v}; - case 4: - return COLOR{t, p, v}; - default: - return COLOR{v, p, q}; - } + float h0 = ((y1 - y2) * (x - x2) + (x2 - x1) * (y - y2)) / S; + float h1 = ((y2 - y0) * (x - x2) + (x0 - x2) * (y - y2)) / S; + float h2 = 1 - h0 - h1; + float r = h0 * C0.red + h1 * C1.red + h2 * C2.red; + float g = h0 * C0.green + h1 * C1.green + h2 * C2.green; + float b = h0 * C0.blue + h1 * C1.blue + h2 * C2.blue; + float a = h0 * C0.alpha + h1 * C1.alpha + h2 * C2.alpha; + return COLOR(r, g, b, a); } + + +COLOR RadialBrush::get_color(int x, int y) const { + double dx = (double) x - cx, dy = (double) y - cy; + double radius = sqrt(dx * dx + dy * dy); + float h0 = (sin(radius / 10 + angle) + 1.0f) / 2; + float h1 = 1 - h0; + float r = h0 * C0.red + h1 * C1.red; + float g = h0 * C0.green + h1 * C1.green; + float b = h0 * C0.blue + h1 * C1.blue; + return COLOR(r, g, b); +} \ No newline at end of file diff --git a/src/shaders.hpp b/src/shaders.hpp new file mode 100644 index 0000000..460f92e --- /dev/null +++ b/src/shaders.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "painter.hpp" +#include "drawing_assets.hpp" + +class BarycentricInterpolator : public Shader { +private: + float x0, y0, x1, y1, x2, y2, S; + COLOR C0, C1, C2; +public: + inline BarycentricInterpolator(float _x0, float _y0, float _x1, float _y1, float _x2, float _y2, COLOR A0, COLOR A1, COLOR A2) : + x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2), + S((_y1 - _y2) * (_x0 - _x2) + (_x2 - _x1) * (_y0 - _y2)), + C0(A0), C1(A1), C2(A2) {} + + inline BarycentricInterpolator(Point p0, Point p1, Point p2, COLOR A0, COLOR A1, COLOR A2) : + BarycentricInterpolator(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, A0, A1, A2) {} + + COLOR get_color(int x, int y) const override; +}; + + +class RadialBrush : public Shader { +private: + float cx, cy; + COLOR C0, C1; + float angle; +public: + RadialBrush(float _x0, float _y0, float _x1, float _y1, COLOR A0, COLOR A1, float _angle) : + cx((_x0 + _x1) / 2.0f), cy((_y0 + _y1) / 2.0f), + C0(A0), C1(A1), angle(_angle) {} + + + COLOR get_color(int x, int y) const override; +}; \ No newline at end of file