#include #include #include #include #include #include #include #include #include static const char vertex_source[] = R"( 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"( in vec4 color; out vec4 out_color; void main() { out_color = color; } )"; static const char text_vertex_source[] = R"( 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"( 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"( 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"( 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 { static std::string shader_prefix = gl::sys::shader_prefix(); struct painter::impl { struct vertex { math::point position; color_rgba color; }; struct text_vertex { math::point position; color_rgba color; math::point texcoord; }; struct texture_vertex { math::point position; math::point texcoord; }; gfx::program program{shader_prefix + vertex_source, shader_prefix + fragment_source}; gfx::program text_program{shader_prefix + text_vertex_source, shader_prefix + text_fragment_source}; gfx::program texture_program{shader_prefix + texture_vertex_source, shader_prefix + texture_fragment_source}; gfx::mesh mesh; gfx::mesh text_mesh; gfx::mesh texture_mesh; std::vector vertices; std::vector indices; std::vector text_vertices; std::vector text_indices; struct texture_render_data { std::vector vertices; std::vector indices; texture_2d const * texture; color_rgba c; }; std::vector textures; texture_2d font_texture; impl() { mesh.setup, gfx::normalized>(); text_mesh.setup, gfx::normalized, math::point>(); texture_mesh.setup, gfx::normalized>>(); font_texture.load(gfx::read_image(io::memory_istream{resource::font_9x12_png.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); 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(math::point const & p0, math::point const & p1, math::point const & p2, color const & c) { triangle(p0, p1, p2, c, c, c); } void painter::triangle(math::point const & p0, math::point const & p1, math::point 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(math::box 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(math::point const & p, float r, color const & c, int quality) { circle(p, r, c, c, quality); } void painter::circle(math::point const & p, float r, color const & c0, color const & c1, int quality) { std::uint32_t const base = impl().vertices.size(); impl().vertices.push_back({{p[0], p[1], 0.f}, c0}); for (int i = 0; i < quality; ++i) { float const a = (math::pi * 2.f * i) / quality; impl().vertices.push_back({{p[0] + r * std::cos(a), p[1] + r * std::sin(a), 0.f}, c1}); } 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(math::point const & p0, math::point const & p1, float width, color const & c, bool smooth) { line(p0, p1, width, width, c, c, smooth); } void painter::line(math::point const & p0, math::point const & p1, float width0, float width1, color const & c0, color const & c1, bool smooth) { std::uint32_t const base = impl().vertices.size(); float const r0 = width0 / 2.f; float const r1 = width1 / 2.f; auto const d = math::normalized(p1 - p0); math::vector const o { -d[1], d[0] }; impl().vertices.push_back({{p0[0] + r0 * o[0], p0[1] + r0 * o[1], 0.f}, c0}); impl().vertices.push_back({{p0[0] - r0 * o[0], p0[1] - r0 * o[1], 0.f}, c0}); impl().vertices.push_back({{p1[0] + r1 * o[0], p1[1] + r1 * o[1], 0.f}, c1}); impl().vertices.push_back({{p1[0] - r1 * o[0], p1[1] - r1 * o[1], 0.f}, c1}); 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, r0, c0); circle(p1, r1, c1); } } void painter::besier(math::point const & p0, math::point const & p1, math::point const & p2, float width, color const & c, int quality, bool smooth) { auto at = [&](float t) { return p0 + (2.f * t) * (p1 - p0) + (t * t) * ((p2 - p1) + (p0 - p1)); }; for (int i = 0; i < quality; ++i) { float t0 = (i * 1.f) / quality; float t1 = ((i + 1) * 1.f) / quality; line(at(t0), at(t1), width, c, smooth); } } void painter::polygon(util::span const> points, color const & c) { auto dcel = cg::ear_clipping(points.begin(), points.end()); for (std::uint32_t i = 0; i < dcel.faces.size(); ++i) { auto e = dcel.face(i).edge(); auto p0 = points[e.origin().index()]; e = e.next(); auto p1 = points[e.origin().index()]; e = e.next(); auto p2 = points[e.origin().index()]; triangle(p0, p1, p2, c); } } math::vector painter::text_size(std::string_view str, font f) { // TODO: multiline text math::vector font_size; switch (f) { case font::font_9x12: font_size = {9.f, 12.f}; break; default: throw util::unknown_enum_value_exception(f); } int max_line_width = 0; int line_count = 0; int last_line_start = 0; for (int i = 0; i < str.size(); ++i) { if (str[i] == '\n') { math::make_max(max_line_width, i - last_line_start); last_line_start = i; ++line_count; } } if (last_line_start + 1 != str.size()) { math::make_max(max_line_width, static_cast(str.size()) - last_line_start); ++line_count; } return {font_size[0] * max_line_width, font_size[1] * line_count}; } void painter::text(math::point const & p, std::string_view str, text_options const & opts) { text3d(math::point{p[0], p[1], 0.f}, str, opts, math::matrix::identity()); } void painter::texture(gfx::texture_2d const & texture, math::box 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(math::point const & p, float length, float width) { math::vector 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(math::point const & p, float radius, color const & c, int quality) { std::uint32_t const base = impl().vertices.size(); impl().vertices.push_back({p - math::vector{0.f, 0.f, radius}, c}); impl().vertices.push_back({p + math::vector{0.f, 0.f, radius}, c}); auto const ray = [](float ax, float ay) { return math::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 * math::pi) / quality / 2.f; float ay = (y * math::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(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(math::point const & p0, math::point const & p1, float width, color const & c) { std::uint32_t const base = impl().vertices.size(); float const r = width / 2.f; auto const d = math::normalized(p1 - p0); math::vector const o = math::normalized(math::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(math::point const & p, std::string_view str, text_options const & opts, math::matrix const & t) { auto const size = math::pointwise_mult(text_size(str, opts.f), opts.scale); math::vector origin { 0.f, 0.f, 0.f }; switch (opts.x) { case x_align::left: break; case x_align::center: origin[0] -= size[0] / 2.f; break; case x_align::right: origin[0] -= size[0]; break; default: throw util::unknown_enum_value_exception(opts.x); } switch (opts.y) { case y_align::top: break; case y_align::center: origin[1] -= size[1] / 2.f; break; case y_align::bottom: origin[1] -= size[1]; break; default: throw util::unknown_enum_value_exception(opts.y); } auto pen = origin; math::vector const sx = {9.f * opts.scale[0], 0.f, 0.f}; math::vector const sy = {0.f, 12.f * opts.scale[1], 0.f}; auto to_texcoord = [](int tx, int ty, int ix, int iy) { return math::point{ tx * 11 + (ix == 0 ? 1 : 10), ty * 14 + (iy == 0 ? 1 : 13) }; }; for (char c : str) { if (c == '\n') { pen[0] = origin[0]; pen += sy; continue; } // 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(math::matrix 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(); } }