psemek/libs/gfx/source/painter.cpp

316 lines
7.8 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.hpp>
#include <psemek/geom/constants.hpp>
#include <psemek/util/memory_stream.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;
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;
}
)";
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);
}
)";
namespace psemek::gfx
{
struct painter::impl
{
struct vertex
{
geom::point<float, 3> position;
color_rgba color;
};
struct text_vertex
{
geom::point<float, 2> position;
color_rgba color;
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::indexed_mesh mesh;
gfx::indexed_mesh text_mesh;
std::vector<vertex> vertices;
std::vector<std::uint32_t> indices;
std::vector<text_vertex> text_vertices;
std::vector<std::uint32_t> text_indices;
gfx::texture_2d font_texture;
impl()
{
mesh.setup<geom::vector<float, 3>, gfx::normalized<color_rgba>>();
text_mesh.setup<geom::point<float, 2>, gfx::normalized<color_rgba>, gfx::normalized<geom::point<std::uint16_t, 2>>>();
util::memory_istream font_data(resource::font_9x12);
font_texture.load(gfx::read_pbm(font_data));
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);
}
};
painter::painter()
: pimpl_{std::make_unique<struct 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)
{
std::uint32_t const base = impl().vertices.size();
impl().vertices.push_back({{p0[0], p0[1], 0.f}, c});
impl().vertices.push_back({{p1[0], p1[1], 0.f}, c});
impl().vertices.push_back({{p2[0], p2[1], 0.f}, c});
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)
{
std::uint32_t const base = impl().vertices.size();
int const quality = 24;
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)
{
auto const size = text_size(str, opts.f, opts.scale);
auto pen = p;
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, 2> const sx = {9.f * opts.scale, 0.f};
geom::vector<float, 2> const sy = {0.f, 12.f * opts.scale};
auto to_texcoord = [](int tx, int ty)
{
return geom::point<std::uint16_t, 2>{ (tx * 65535) / 16, (ty * 65535) / 6 };
};
for (char c : str)
{
if (c < 32 || c > 128) c = '?';
int ty = (c - 32) / 16;
int tx = (c - 32) % 16;
std::uint32_t const base = impl().text_vertices.size();
geom::point<float, 2> position;
color_rgba color;
geom::point<std::uint16_t, 2> texcoord;
impl().text_vertices.push_back({pen, opts.c, to_texcoord(tx, ty)});
impl().text_vertices.push_back({pen + sx, opts.c, to_texcoord(tx + 1, ty)});
impl().text_vertices.push_back({pen + sy, opts.c, to_texcoord(tx, ty + 1)});
impl().text_vertices.push_back({pen + sx + sy, opts.c, to_texcoord(tx + 1, ty + 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)
{
impl().mesh.load(impl().vertices, impl().indices, gl::STREAM_DRAW);
impl().vertices.clear();
impl().indices.clear();
impl().text_mesh.load(impl().text_vertices, impl().text_indices, gl::STREAM_DRAW);
impl().text_vertices.clear();
impl().text_indices.clear();
impl().program.bind();
impl().program["u_transform"] = transform;
impl().mesh.draw(gl::TRIANGLES);
impl().text_program.bind();
impl().text_program["u_transform"] = transform;
impl().text_program["u_texture"] = 0;
impl().font_texture.bind();
impl().text_mesh.draw(gl::TRIANGLES);
}
}