diff --git a/CMakeLists.txt b/CMakeLists.txt index 29a9e9b..2dc4019 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,6 @@ add_library(picoled include/picoled/SSD1306.h src/picoled.cpp - src/buffer.cpp src/2D.cpp src/SSD1306.cpp ) diff --git a/include/picoled.h b/include/picoled.h index baf539e..13fbcb1 100644 --- a/include/picoled.h +++ b/include/picoled.h @@ -32,7 +32,7 @@ public: uint8_t grayscale() const { - uint16_t unscaled = red() + green() + blue(); + uint16_t unscaled = static_cast(r) + static_cast(g) + static_cast(b); return unscaled / 3; } diff --git a/include/picoled/buffer.h b/include/picoled/buffer.h index 4ad04ea..a5b74ab 100644 --- a/include/picoled/buffer.h +++ b/include/picoled/buffer.h @@ -6,23 +6,24 @@ namespace picoled { +// behavior when out of bounds +enum class buffer_read_mode +{ + empty, // use default constructor for T + wrap, // use modular arithmetic to put back in bounds + clamp // pick from the corresponding edge +}; + template class buffer { public: - // behavior when out of bounds - enum class ReadMode - { - Default, // use default constructor for T - Wrap, // use modular arithmetic to put back in bounds - Clamp // pick from the corresponding edge - }; - buffer(size_t width, size_t height) : width(width), height(height), data(new T[width * height]) - {} + { + } ~buffer() { delete[] data; @@ -44,17 +45,22 @@ public: data[x + y * width] = pixel; } - T read(int x, int y, ReadMode mode = ReadMode::Default) + T read_uv(float x, float y, buffer_read_mode mode = buffer_read_mode::clamp) const + { + return read(x * width, y * height, mode); + } + + T read(int x, int y, buffer_read_mode mode = buffer_read_mode::clamp) const { switch (mode) { - case ReadMode::Default: + case buffer_read_mode::empty: return read_default(x, y); - case ReadMode::Wrap: + case buffer_read_mode::wrap: return read_wrap(x, y); - case ReadMode::Clamp: + case buffer_read_mode::clamp: return read_clamp(x, y); } @@ -74,13 +80,13 @@ private: size_t height; T* data; - T read_default(int x, int y) + T read_default(int x, int y) const { if (x < 0 || x >= width || y < 0 || y >= height) { return T(); } return data[x + y * width]; } - T read_wrap(int x, int y) + T read_wrap(int x, int y) const { if (x < 0) { @@ -103,7 +109,7 @@ private: return data[x + y * width]; } - T read_clamp(int x, int y) + T read_clamp(int x, int y) const { if (x < 0) { x = 0; } else if (x >= width) { x = width - 1; } diff --git a/include/picoled/gfx/2D.h b/include/picoled/gfx/2D.h index fddf2dd..2e445fa 100644 --- a/include/picoled/gfx/2D.h +++ b/include/picoled/gfx/2D.h @@ -21,18 +21,53 @@ public: float operator^(const vec2& other) const { return x * other.y - y * other.x; } }; +class vec3 +{ +public: + float x; + float y; + float z; + + vec3 operator+(const vec3& other) const { return { x + other.x, y + other.y, z + other.z }; } + vec3 operator-(const vec3& other) const { return { x - other.x, y - other.y, z - other.z }; } + vec3 operator*(const vec3& other) const { return { x * other.x, y * other.y, z * other.z }; } + vec3 operator/(const vec3& other) const { return { x / other.x, y / other.y, z / other.z }; } + + float operator%(const vec3& other) const { return x * other.x + y * other.y + z * other.z; } + vec3 operator^(const vec3& other) const + { + return + { + y * other.z - z * other.y, + z * other.x - x * other.z, + x * other.y - y * other.x + }; + } +}; + +class vertex2 +{ +public: + vec2 position; + vec2 texture_coordinates; +}; + class triangle { public: - vec2 a; - vec2 b; - vec2 c; + vertex2 a; + vertex2 b; + vertex2 c; triangle fix_winding() const; - bool inside(const vec2& p) const; + vec3 barycentric(const vec2& p) const; + bool contains(const vec2& p) const; vec2 min() const; vec2 max() const; + +private: + float area_double() const; }; class gfx2d @@ -48,7 +83,7 @@ public: gfx2d& operator=(const gfx2d&) = delete; gfx2d& operator=(gfx2d&&) = delete; - void draw_triangle(triangle tri, pixel p = 0xFF, bool ignore_winding = true); + void draw_triangle(triangle tri, const buffer& texture, bool ignore_winding = true); private: oled* screen; diff --git a/src/2D.cpp b/src/2D.cpp index 345ba0e..64f57a9 100644 --- a/src/2D.cpp +++ b/src/2D.cpp @@ -1,13 +1,16 @@ #include "picoled/gfx/2D.h" +#include "picoled/buffer.h" #include +#include + namespace picoled::gfx { triangle triangle::fix_winding() const { - if (((b - a) ^ (c - a)) < 0) + if (((b.position - a.position) ^ (c.position - a.position)) < 0) { return { a, c, b }; } @@ -17,11 +20,19 @@ triangle triangle::fix_winding() const } } -bool triangle::inside(const vec2& p) const +vec3 triangle::barycentric(const vec2& p) const { - const float abp = (b - a) ^ (p - a); - const float bcp = (c - b) ^ (p - b); - const float cap = (a - c) ^ (p - c); + const float total = area_double(); + const float u = triangle{ b, a, p }.area_double() / total; + const float v = triangle{ a, c, p }.area_double() / total; + return vec3{ u, v, 1 - u - v }; +} + +bool triangle::contains(const vec2& p) const +{ + const float abp = (b.position - a.position) ^ (p - a.position); + const float bcp = (c.position - b.position) ^ (p - b.position); + const float cap = (a.position - c.position) ^ (p - c.position); return abp >= 0 && bcp >= 0 && cap >= 0; } @@ -30,8 +41,8 @@ vec2 triangle::min() const { return { - fminf(fminf(a.x, b.x), c.x), - fminf(fminf(a.y, b.y), c.y) + fminf(fminf(a.position.x, b.position.x), c.position.x), + fminf(fminf(a.position.y, b.position.y), c.position.y) }; } @@ -39,12 +50,19 @@ vec2 triangle::max() const { return { - fmaxf(fmaxf(a.x, b.x), c.x), - fmaxf(fmaxf(a.y, b.y), c.y) + fmaxf(fmaxf(a.position.x, b.position.x), c.position.x), + fmaxf(fmaxf(a.position.y, b.position.y), c.position.y) }; } -void gfx2d::draw_triangle(triangle tri, pixel p, bool ignore_winding) +float triangle::area_double() const +{ + // don't divide by two since it would cancel out + // in the barycentric coordinate calculation anyway + return std::abs((b.position - a.position) ^ (c.position - a.position)); +} + +void gfx2d::draw_triangle(triangle tri, const buffer& texture, bool ignore_winding) { if (ignore_winding) { tri = tri.fix_winding(); } @@ -55,7 +73,15 @@ void gfx2d::draw_triangle(triangle tri, pixel p, bool ignore_winding) { for (float j = min.y; j <= max.y; j += 0.75f) { - if (tri.inside({ i, j })) { screen->write(i, j, p); } + const vec2 position = { i, j }; + if (tri.contains(position)) + { + const vec3 barycentric = tri.barycentric(position); + const float u = barycentric % vec3{ tri.a.texture_coordinates.x, tri.b.texture_coordinates.x, tri.c.texture_coordinates.x }; + const float v = barycentric % vec3{ tri.a.texture_coordinates.y, tri.b.texture_coordinates.y, tri.c.texture_coordinates.y }; + const pixel read = texture.read_uv(u, v, buffer_read_mode::clamp); + screen->write(i, j, read); + } } } } diff --git a/src/SSD1306.cpp b/src/SSD1306.cpp index 566dada..04d10f4 100644 --- a/src/SSD1306.cpp +++ b/src/SSD1306.cpp @@ -81,7 +81,7 @@ ssd1306::~ssd1306() void ssd1306::update_impl() { // clear display to zero - memset(&buffer[1], 0, get_width() * get_height() * sizeof(*buffer)); + memset(&buffer[1], 0, get_width() * get_height() * sizeof(*buffer) / 8); for (int x = 0; x < get_width(); x++) { diff --git a/src/buffer.cpp b/src/buffer.cpp deleted file mode 100644 index e69de29..0000000