Add texturing
This commit is contained in:
parent
10e9176d13
commit
dfeccd50b9
@ -7,7 +7,6 @@ add_library(picoled
|
|||||||
include/picoled/SSD1306.h
|
include/picoled/SSD1306.h
|
||||||
|
|
||||||
src/picoled.cpp
|
src/picoled.cpp
|
||||||
src/buffer.cpp
|
|
||||||
src/2D.cpp
|
src/2D.cpp
|
||||||
src/SSD1306.cpp
|
src/SSD1306.cpp
|
||||||
)
|
)
|
||||||
|
@ -32,7 +32,7 @@ public:
|
|||||||
|
|
||||||
uint8_t grayscale() const
|
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;
|
return unscaled / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,23 +6,24 @@
|
|||||||
namespace picoled
|
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>
|
template <typename T>
|
||||||
class buffer
|
class buffer
|
||||||
{
|
{
|
||||||
public:
|
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) :
|
buffer(size_t width, size_t height) :
|
||||||
width(width),
|
width(width),
|
||||||
height(height),
|
height(height),
|
||||||
data(new T[width * height])
|
data(new T[width * height])
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
~buffer()
|
~buffer()
|
||||||
{
|
{
|
||||||
delete[] data;
|
delete[] data;
|
||||||
@ -44,17 +45,22 @@ public:
|
|||||||
data[x + y * width] = pixel;
|
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)
|
switch (mode)
|
||||||
{
|
{
|
||||||
case ReadMode::Default:
|
case buffer_read_mode::empty:
|
||||||
return read_default(x, y);
|
return read_default(x, y);
|
||||||
|
|
||||||
case ReadMode::Wrap:
|
case buffer_read_mode::wrap:
|
||||||
return read_wrap(x, y);
|
return read_wrap(x, y);
|
||||||
|
|
||||||
case ReadMode::Clamp:
|
case buffer_read_mode::clamp:
|
||||||
return read_clamp(x, y);
|
return read_clamp(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,13 +80,13 @@ private:
|
|||||||
size_t height;
|
size_t height;
|
||||||
T* data;
|
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(); }
|
if (x < 0 || x >= width || y < 0 || y >= height) { return T(); }
|
||||||
return data[x + y * width];
|
return data[x + y * width];
|
||||||
}
|
}
|
||||||
|
|
||||||
T read_wrap(int x, int y)
|
T read_wrap(int x, int y) const
|
||||||
{
|
{
|
||||||
if (x < 0)
|
if (x < 0)
|
||||||
{
|
{
|
||||||
@ -103,7 +109,7 @@ private:
|
|||||||
return data[x + y * width];
|
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; }
|
if (x < 0) { x = 0; }
|
||||||
else if (x >= width) { x = width - 1; }
|
else if (x >= width) { x = width - 1; }
|
||||||
|
@ -21,18 +21,53 @@ public:
|
|||||||
float operator^(const vec2& other) const { return x * other.y - y * other.x; }
|
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
|
class triangle
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
vec2 a;
|
vertex2 a;
|
||||||
vec2 b;
|
vertex2 b;
|
||||||
vec2 c;
|
vertex2 c;
|
||||||
|
|
||||||
triangle fix_winding() const;
|
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 min() const;
|
||||||
vec2 max() const;
|
vec2 max() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float area_double() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class gfx2d
|
class gfx2d
|
||||||
@ -48,7 +83,7 @@ public:
|
|||||||
gfx2d& operator=(const gfx2d&) = delete;
|
gfx2d& operator=(const gfx2d&) = delete;
|
||||||
gfx2d& operator=(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:
|
private:
|
||||||
oled* screen;
|
oled* screen;
|
||||||
|
48
src/2D.cpp
48
src/2D.cpp
@ -1,13 +1,16 @@
|
|||||||
#include "picoled/gfx/2D.h"
|
#include "picoled/gfx/2D.h"
|
||||||
|
#include "picoled/buffer.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <pico/stdio.h>
|
||||||
|
|
||||||
namespace picoled::gfx
|
namespace picoled::gfx
|
||||||
{
|
{
|
||||||
|
|
||||||
triangle triangle::fix_winding() const
|
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 };
|
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 total = area_double();
|
||||||
const float bcp = (c - b) ^ (p - b);
|
const float u = triangle{ b, a, p }.area_double() / total;
|
||||||
const float cap = (a - c) ^ (p - c);
|
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;
|
return abp >= 0 && bcp >= 0 && cap >= 0;
|
||||||
}
|
}
|
||||||
@ -30,8 +41,8 @@ vec2 triangle::min() const
|
|||||||
{
|
{
|
||||||
return
|
return
|
||||||
{
|
{
|
||||||
fminf(fminf(a.x, b.x), c.x),
|
fminf(fminf(a.position.x, b.position.x), c.position.x),
|
||||||
fminf(fminf(a.y, b.y), c.y)
|
fminf(fminf(a.position.y, b.position.y), c.position.y)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,12 +50,19 @@ vec2 triangle::max() const
|
|||||||
{
|
{
|
||||||
return
|
return
|
||||||
{
|
{
|
||||||
fmaxf(fmaxf(a.x, b.x), c.x),
|
fmaxf(fmaxf(a.position.x, b.position.x), c.position.x),
|
||||||
fmaxf(fmaxf(a.y, b.y), c.y)
|
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(); }
|
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)
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ ssd1306::~ssd1306()
|
|||||||
void ssd1306::update_impl()
|
void ssd1306::update_impl()
|
||||||
{
|
{
|
||||||
// clear display to zero
|
// 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++)
|
for (int x = 0; x < get_width(); x++)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user