581 lines
15 KiB
C++
581 lines
15 KiB
C++
#include <psemek/gfx/painter.hpp>
|
|
#include <psemek/gfx/program.hpp>
|
|
#include <psemek/gfx/mesh.hpp>
|
|
#include <psemek/gfx/texture.hpp>
|
|
#include <psemek/gfx/resource/font_9x12_png.hpp>
|
|
#include <psemek/geom/constants.hpp>
|
|
#include <psemek/io/memory_stream.hpp>
|
|
#include <psemek/log/log.hpp>
|
|
|
|
static const char vertex_source[] =
|
|
R"(#version 330
|
|
|
|
uniform mat4 u_transform;
|
|
|
|
layout (location = 0) in vec4 in_position;
|
|
layout (location = 1) in vec4 in_color;
|
|
|
|
out vec4 color;
|
|
|
|
void main()
|
|
{
|
|
gl_Position = u_transform * in_position;
|
|
color = in_color;
|
|
}
|
|
)";
|
|
|
|
static const char fragment_source[] =
|
|
R"(#version 330
|
|
|
|
in vec4 color;
|
|
|
|
out vec4 out_color;
|
|
|
|
void main()
|
|
{
|
|
out_color = color;
|
|
}
|
|
)";
|
|
|
|
static const char text_vertex_source[] =
|
|
R"(#version 330
|
|
|
|
uniform mat4 u_transform;
|
|
uniform uvec2 u_texture_size;
|
|
|
|
layout (location = 0) in vec4 in_position;
|
|
layout (location = 1) in vec4 in_color;
|
|
layout (location = 2) in vec2 in_texcoord;
|
|
|
|
out vec4 color;
|
|
out vec2 texcoord;
|
|
|
|
void main()
|
|
{
|
|
gl_Position = u_transform * in_position;
|
|
color = in_color;
|
|
texcoord = in_texcoord / vec2(u_texture_size);
|
|
}
|
|
)";
|
|
|
|
static const char text_fragment_source[] =
|
|
R"(#version 330
|
|
|
|
uniform sampler2D u_texture;
|
|
|
|
in vec4 color;
|
|
in vec2 texcoord;
|
|
|
|
out vec4 out_color;
|
|
|
|
void main()
|
|
{
|
|
out_color = color * texture(u_texture, texcoord);
|
|
}
|
|
)";
|
|
|
|
static const char texture_vertex_source[] =
|
|
R"(#version 330
|
|
|
|
uniform mat4 u_transform;
|
|
|
|
layout (location = 0) in vec4 in_position;
|
|
layout (location = 1) in vec2 in_texcoord;
|
|
|
|
out vec2 texcoord;
|
|
|
|
void main()
|
|
{
|
|
gl_Position = u_transform * in_position;
|
|
texcoord = in_texcoord;
|
|
}
|
|
)";
|
|
|
|
static const char texture_fragment_source[] =
|
|
R"(#version 330
|
|
|
|
uniform sampler2D u_texture;
|
|
uniform vec4 u_color;
|
|
|
|
in vec2 texcoord;
|
|
|
|
out vec4 out_color;
|
|
|
|
void main()
|
|
{
|
|
vec4 color = texture(u_texture, texcoord);
|
|
color.rgb = mix(color.rgb, u_color.rgb, u_color.a);
|
|
out_color = color;
|
|
}
|
|
)";
|
|
|
|
namespace psemek::gfx
|
|
{
|
|
|
|
struct painter::impl
|
|
{
|
|
struct vertex
|
|
{
|
|
geom::point<float, 3> position;
|
|
color_rgba color;
|
|
};
|
|
|
|
struct text_vertex
|
|
{
|
|
geom::point<float, 3> position;
|
|
color_rgba color;
|
|
geom::point<std::uint16_t, 2> texcoord;
|
|
};
|
|
|
|
struct texture_vertex
|
|
{
|
|
geom::point<float, 3> position;
|
|
geom::point<std::uint16_t, 2> texcoord;
|
|
};
|
|
|
|
gfx::program program{vertex_source, fragment_source};
|
|
gfx::program text_program{text_vertex_source, text_fragment_source};
|
|
gfx::program texture_program{texture_vertex_source, texture_fragment_source};
|
|
gfx::mesh mesh;
|
|
gfx::mesh text_mesh;
|
|
gfx::mesh texture_mesh;
|
|
|
|
std::vector<vertex> vertices;
|
|
std::vector<std::uint32_t> indices;
|
|
|
|
std::vector<text_vertex> text_vertices;
|
|
std::vector<std::uint32_t> text_indices;
|
|
|
|
struct texture_render_data
|
|
{
|
|
std::vector<texture_vertex> vertices;
|
|
std::vector<std::uint32_t> indices;
|
|
texture_2d const * texture;
|
|
color_rgba c;
|
|
};
|
|
|
|
std::vector<texture_render_data> textures;
|
|
|
|
texture_2d font_texture;
|
|
|
|
impl()
|
|
{
|
|
mesh.setup<geom::vector<float, 3>, gfx::normalized<color_rgba>>();
|
|
text_mesh.setup<geom::point<float, 3>, gfx::normalized<color_rgba>, geom::point<std::uint16_t, 2>>();
|
|
texture_mesh.setup<geom::point<float, 3>, gfx::normalized<geom::point<std::uint16_t, 2>>>();
|
|
|
|
font_texture.load(gfx::read_png_monochrome(io::memory_istream{resource::font_9x12_png}));
|
|
|
|
font_texture.bind();
|
|
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_G, gl::RED);
|
|
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_B, gl::RED);
|
|
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_A, gl::RED);
|
|
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST);
|
|
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST);
|
|
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE);
|
|
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE);
|
|
}
|
|
};
|
|
|
|
painter::painter()
|
|
: pimpl_{make_impl()}
|
|
{}
|
|
|
|
painter::~painter() = default;
|
|
|
|
void painter::triangle(geom::point<float, 2> const & p0, geom::point<float, 2> const & p1, geom::point<float, 2> const & p2, color const & c)
|
|
{
|
|
triangle(p0, p1, p2, c, c, c);
|
|
}
|
|
|
|
void painter::triangle(geom::point<float, 2> const & p0, geom::point<float, 2> const & p1, geom::point<float, 2> const & p2, color const & c0, color const & c1, color const & c2)
|
|
{
|
|
std::uint32_t const base = impl().vertices.size();
|
|
|
|
impl().vertices.push_back({{p0[0], p0[1], 0.f}, c0});
|
|
impl().vertices.push_back({{p1[0], p1[1], 0.f}, c1});
|
|
impl().vertices.push_back({{p2[0], p2[1], 0.f}, c2});
|
|
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 1);
|
|
impl().indices.push_back(base + 2);
|
|
}
|
|
|
|
void painter::rect(geom::box<float, 2> const & box, color const & c)
|
|
{
|
|
std::uint32_t const base = impl().vertices.size();
|
|
|
|
impl().vertices.push_back({{box[0].min, box[1].min, 0.f}, c});
|
|
impl().vertices.push_back({{box[0].max, box[1].min, 0.f}, c});
|
|
impl().vertices.push_back({{box[0].min, box[1].max, 0.f}, c});
|
|
impl().vertices.push_back({{box[0].max, box[1].max, 0.f}, c});
|
|
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 1);
|
|
impl().indices.push_back(base + 3);
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 3);
|
|
impl().indices.push_back(base + 2);
|
|
}
|
|
|
|
void painter::circle(geom::point<float, 2> const & p, float r, color const & c, int quality)
|
|
{
|
|
std::uint32_t const base = impl().vertices.size();
|
|
|
|
impl().vertices.push_back({{p[0], p[1], 0.f}, c});
|
|
for (int i = 0; i < quality; ++i)
|
|
{
|
|
float const a = (geom::pi * 2.f * i) / quality;
|
|
impl().vertices.push_back({{p[0] + r * std::cos(a), p[1] + r * std::sin(a), 0.f}, c});
|
|
}
|
|
|
|
for (int i = 0; i < quality; ++i)
|
|
{
|
|
impl().indices.push_back(base);
|
|
impl().indices.push_back(base + 1 + i);
|
|
impl().indices.push_back(base + 1 + ((i + 1) % quality));
|
|
}
|
|
}
|
|
|
|
void painter::line(geom::point<float, 2> const & p0, geom::point<float, 2> const & p1, float width, color const & c, bool smooth)
|
|
{
|
|
std::uint32_t const base = impl().vertices.size();
|
|
float const r = width / 2.f;
|
|
|
|
auto const d = geom::normalized(p1 - p0);
|
|
geom::vector<float, 2> const o { -d[1], d[0] };
|
|
|
|
impl().vertices.push_back({{p0[0] + r * o[0], p0[1] + r * o[1], 0.f}, c});
|
|
impl().vertices.push_back({{p0[0] - r * o[0], p0[1] - r * o[1], 0.f}, c});
|
|
impl().vertices.push_back({{p1[0] + r * o[0], p1[1] + r * o[1], 0.f}, c});
|
|
impl().vertices.push_back({{p1[0] - r * o[0], p1[1] - r * o[1], 0.f}, c});
|
|
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 1);
|
|
impl().indices.push_back(base + 3);
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 3);
|
|
impl().indices.push_back(base + 2);
|
|
|
|
if (smooth)
|
|
{
|
|
circle(p0, r, c);
|
|
circle(p1, r, c);
|
|
}
|
|
}
|
|
|
|
geom::vector<float, 2> painter::text_size(std::string_view str, font f, float scale)
|
|
{
|
|
// TODO: multiline text
|
|
geom::vector<float, 2> s;
|
|
switch (f)
|
|
{
|
|
case font::font_9x12:
|
|
s = {9.f, 12.f};
|
|
break;
|
|
default:
|
|
throw std::runtime_error("Unknown font");
|
|
}
|
|
|
|
s[0] *= str.size() * scale;
|
|
s[1] *= scale;
|
|
return s;
|
|
}
|
|
|
|
void painter::text(geom::point<float, 2> const & p, std::string_view str, text_options const & opts)
|
|
{
|
|
text3d(geom::point{p[0], p[1], 0.f}, str, opts, geom::matrix<float, 3, 3>::identity());
|
|
}
|
|
|
|
void painter::texture(gfx::texture_2d const & texture, geom::box<float, 2> const & box, color const & c)
|
|
{
|
|
impl::texture_render_data data;
|
|
data.texture = &texture;
|
|
data.c = c;
|
|
|
|
data.indices = {0, 1, 2, 2, 1, 3};
|
|
|
|
std::uint16_t lo = 0;
|
|
std::uint16_t hi = 65535;
|
|
|
|
data.vertices.push_back({{box[0].min, box[1].min, 0.f}, {lo, lo}});
|
|
data.vertices.push_back({{box[0].max, box[1].min, 0.f}, {hi, lo}});
|
|
data.vertices.push_back({{box[0].min, box[1].max, 0.f}, {lo, hi}});
|
|
data.vertices.push_back({{box[0].max, box[1].max, 0.f}, {hi, hi}});
|
|
|
|
impl().textures.push_back(data);
|
|
}
|
|
|
|
void painter::axes(geom::point<float, 3> const & p, float length, float width)
|
|
{
|
|
geom::vector<float, 3> const d[3]
|
|
{
|
|
{ 1.f, 0.f, 0.f },
|
|
{ 0.f, 1.f, 0.f },
|
|
{ 0.f, 0.f, 1.f },
|
|
};
|
|
|
|
color const c[3]
|
|
{
|
|
{ 255, 0, 0, 255 },
|
|
{ 0, 255, 0, 255 },
|
|
{ 0, 0, 255, 255 },
|
|
};
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
int j = (i + 1) % 3;
|
|
int k = (i + 2) % 3;
|
|
|
|
std::uint32_t const base = impl().vertices.size();
|
|
|
|
impl().vertices.push_back({p + d[i] * length, c[i]});
|
|
impl().vertices.push_back({p + d[j] * width, c[i]});
|
|
impl().vertices.push_back({p + d[k] * width, c[i]});
|
|
impl().vertices.push_back({p - d[j] * width, c[i]});
|
|
impl().vertices.push_back({p - d[k] * width, c[i]});
|
|
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 1);
|
|
impl().indices.push_back(base + 2);
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 2);
|
|
impl().indices.push_back(base + 3);
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 3);
|
|
impl().indices.push_back(base + 4);
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 4);
|
|
impl().indices.push_back(base + 1);
|
|
}
|
|
|
|
std::uint32_t const base = impl().vertices.size();
|
|
|
|
color const white {255, 255, 255, 255};
|
|
|
|
impl().vertices.push_back({p - d[0] * width, white});
|
|
impl().vertices.push_back({p - d[1] * width, white});
|
|
impl().vertices.push_back({p - d[2] * width, white});
|
|
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 2);
|
|
impl().indices.push_back(base + 1);
|
|
}
|
|
|
|
void painter::sphere(geom::point<float, 3> const & p, float radius, color const & c, int quality)
|
|
{
|
|
std::uint32_t const base = impl().vertices.size();
|
|
|
|
impl().vertices.push_back({p - geom::vector{0.f, 0.f, radius}, c});
|
|
impl().vertices.push_back({p + geom::vector{0.f, 0.f, radius}, c});
|
|
|
|
auto const ray = [](float ax, float ay)
|
|
{
|
|
return geom::vector{std::cos(ax) * std::cos(ay), std::sin(ax) * std::cos(ay), std::sin(ay)};
|
|
};
|
|
|
|
for (int y = - quality + 1; y < quality; ++y)
|
|
{
|
|
for (int x = 0; x < quality * 4; ++x)
|
|
{
|
|
float ax = (x * geom::pi) / quality / 2.f;
|
|
float ay = (y * geom::pi) / quality / 2.f;
|
|
|
|
impl().vertices.push_back({p + radius * ray(ax, ay), c});
|
|
}
|
|
}
|
|
|
|
auto const index = [quality, base](int x, int y)
|
|
{
|
|
if (y == -quality)
|
|
return base;
|
|
if (y == quality)
|
|
return base + 1;
|
|
|
|
return static_cast<std::uint32_t>(2 + (y + quality - 1) * 4 * quality + x);
|
|
};
|
|
|
|
for (int x = 0; x < quality * 4; ++x)
|
|
{
|
|
int xx = (x + 1) % (quality * 4);
|
|
|
|
impl().indices.push_back(index(x, - quality + 1));
|
|
impl().indices.push_back(index(x, - quality));
|
|
impl().indices.push_back(index(xx, - quality + 1));
|
|
}
|
|
|
|
for (int y = - quality + 1; y + 1 < quality; ++y)
|
|
{
|
|
for (int x = 0; x < quality * 4; ++x)
|
|
{
|
|
int yy = y + 1;
|
|
int xx = (x + 1) % (quality * 4);
|
|
|
|
impl().indices.push_back(index(x, y));
|
|
impl().indices.push_back(index(xx, y));
|
|
impl().indices.push_back(index(xx, yy));
|
|
|
|
impl().indices.push_back(index(x, y));
|
|
impl().indices.push_back(index(xx, yy));
|
|
impl().indices.push_back(index(x, yy));
|
|
}
|
|
}
|
|
|
|
for (int x = 0; x < quality * 4; ++x)
|
|
{
|
|
int xx = (x + 1) % (quality * 4);
|
|
|
|
impl().indices.push_back(index(x, quality - 1));
|
|
impl().indices.push_back(index(xx, quality - 1));
|
|
impl().indices.push_back(index(x, quality));
|
|
}
|
|
}
|
|
|
|
void painter::line3d(geom::point<float, 3> const & p0, geom::point<float, 3> const & p1, float width, color const & c)
|
|
{
|
|
std::uint32_t const base = impl().vertices.size();
|
|
float const r = width / 2.f;
|
|
|
|
auto const d = geom::normalized(p1 - p0);
|
|
geom::vector<float, 3> const o = geom::normalized(geom::vector{ -d[1], d[0], 0.f });
|
|
|
|
impl().vertices.push_back({{p0[0] + r * o[0], p0[1] + r * o[1], p0[2]}, c});
|
|
impl().vertices.push_back({{p0[0] - r * o[0], p0[1] - r * o[1], p0[2]}, c});
|
|
impl().vertices.push_back({{p1[0] + r * o[0], p1[1] + r * o[1], p1[2]}, c});
|
|
impl().vertices.push_back({{p1[0] - r * o[0], p1[1] - r * o[1], p1[2]}, c});
|
|
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 1);
|
|
impl().indices.push_back(base + 3);
|
|
impl().indices.push_back(base + 0);
|
|
impl().indices.push_back(base + 3);
|
|
impl().indices.push_back(base + 2);
|
|
}
|
|
|
|
void painter::text3d(geom::point<float, 3> const & p, std::string_view str, text_options const & opts, geom::matrix<float, 3, 3> const & t)
|
|
{
|
|
auto const size = text_size(str, opts.f, opts.scale);
|
|
|
|
geom::vector<float, 3> pen { 0.f, 0.f, 0.f };
|
|
|
|
switch (opts.x)
|
|
{
|
|
case x_align::left:
|
|
break;
|
|
case x_align::center:
|
|
pen[0] -= size[0] / 2.f;
|
|
break;
|
|
case x_align::right:
|
|
pen[0] -= size[0];
|
|
break;
|
|
default:
|
|
throw std::runtime_error("Unknown x alignment");
|
|
}
|
|
|
|
switch (opts.y)
|
|
{
|
|
case y_align::top:
|
|
break;
|
|
case y_align::center:
|
|
pen[1] -= size[1] / 2.f;
|
|
break;
|
|
case y_align::bottom:
|
|
pen[1] -= size[1];
|
|
break;
|
|
default:
|
|
throw std::runtime_error("Unknown y alignment");
|
|
}
|
|
|
|
geom::vector<float, 3> const sx = {9.f * opts.scale, 0.f, 0.f};
|
|
geom::vector<float, 3> const sy = {0.f, 12.f * opts.scale, 0.f};
|
|
|
|
auto to_texcoord = [](int tx, int ty, int ix, int iy)
|
|
{
|
|
return geom::point<std::uint16_t, 2>{ tx * 11 + (ix == 0 ? 1 : 10), ty * 14 + (iy == 0 ? 1 : 13) };
|
|
};
|
|
|
|
for (char c : str)
|
|
{
|
|
// Guard against unsigned char
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wtype-limits"
|
|
#endif
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
|
|
#endif
|
|
|
|
if ((c < 32) || (c >= 128)) c = '?';
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
int ty = (c - 32) / 16;
|
|
int tx = (c - 32) % 16;
|
|
|
|
std::uint32_t const base = impl().text_vertices.size();
|
|
|
|
impl().text_vertices.push_back({p + t * pen, opts.c, to_texcoord(tx, ty, 0, 0)});
|
|
impl().text_vertices.push_back({p + t * (pen + sx), opts.c, to_texcoord(tx, ty, 1, 0)});
|
|
impl().text_vertices.push_back({p + t * (pen + sy), opts.c, to_texcoord(tx, ty, 0, 1)});
|
|
impl().text_vertices.push_back({p + t * (pen + sx + sy), opts.c, to_texcoord(tx, ty, 1, 1)});
|
|
|
|
impl().text_indices.push_back(base + 0);
|
|
impl().text_indices.push_back(base + 1);
|
|
impl().text_indices.push_back(base + 3);
|
|
impl().text_indices.push_back(base + 0);
|
|
impl().text_indices.push_back(base + 3);
|
|
impl().text_indices.push_back(base + 2);
|
|
|
|
pen += sx;
|
|
}
|
|
}
|
|
|
|
void painter::render(geom::matrix<float, 4, 4> const & transform)
|
|
{
|
|
gl::ActiveTexture(gl::TEXTURE0);
|
|
|
|
gl::Disable(gl::CULL_FACE);
|
|
|
|
gl::Enable(gl::BLEND);
|
|
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
|
|
|
gl::Disable(gl::DEPTH_TEST);
|
|
|
|
impl().mesh.load(impl().vertices, impl().indices, gl::TRIANGLES, gl::STREAM_DRAW);
|
|
impl().vertices.clear();
|
|
impl().indices.clear();
|
|
|
|
impl().text_mesh.load(impl().text_vertices, impl().text_indices, gl::TRIANGLES, gl::STREAM_DRAW);
|
|
impl().text_vertices.clear();
|
|
impl().text_indices.clear();
|
|
|
|
impl().program.bind();
|
|
impl().program["u_transform"] = transform;
|
|
impl().mesh.draw();
|
|
|
|
impl().text_program.bind();
|
|
impl().text_program["u_transform"] = transform;
|
|
impl().text_program["u_texture"] = 0;
|
|
impl().text_program["u_texture_size"] = impl().font_texture.size();
|
|
impl().font_texture.bind();
|
|
impl().text_mesh.draw();
|
|
|
|
impl().texture_program.bind();
|
|
impl().texture_program["u_transform"] = transform;
|
|
impl().texture_program["u_texture"] = 0;
|
|
for (auto const & data : impl().textures)
|
|
{
|
|
impl().texture_program["u_color"] = to_colorf(data.c);
|
|
impl().texture_mesh.load(data.vertices, data.indices, gl::TRIANGLES, gl::STREAM_DRAW);
|
|
data.texture->bind();
|
|
impl().texture_mesh.draw();
|
|
}
|
|
impl().textures.clear();
|
|
}
|
|
|
|
}
|