Add texturing

This commit is contained in:
shylie 2025-06-09 14:43:30 -04:00
parent 10e9176d13
commit dfeccd50b9
7 changed files with 101 additions and 35 deletions

View File

@ -7,7 +7,6 @@ add_library(picoled
include/picoled/SSD1306.h
src/picoled.cpp
src/buffer.cpp
src/2D.cpp
src/SSD1306.cpp
)

View File

@ -32,7 +32,7 @@ public:
uint8_t grayscale() const
{
uint16_t unscaled = red() + green() + blue();
uint16_t unscaled = static_cast<uint16_t>(r) + static_cast<uint16_t>(g) + static_cast<uint16_t>(b);
return unscaled / 3;
}

View File

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

View File

@ -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<pixel>& texture, bool ignore_winding = true);
private:
oled* screen;

View File

@ -1,13 +1,16 @@
#include "picoled/gfx/2D.h"
#include "picoled/buffer.h"
#include <cmath>
#include <pico/stdio.h>
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<pixel>& 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);
}
}
}
}

View File

@ -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++)
{

View File