💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

306 lines
8.3 KiB

/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include <engine/scene/text.hpp>
#include <engine/render/vertex-attribute.hpp>
#include <engine/type/unicode/convert.hpp>
#include <engine/scene/camera.hpp>
#include <cstddef>
namespace scene {
text::text()
{
// Allocate VBO and VAO
m_vbo = std::make_unique<gl::vertex_buffer>();
m_vao = std::make_unique<gl::vertex_array>();
// Calculate vertex stride
m_vertex_stride = (3 + 2 + 4) * sizeof(float);
// Init vertex attribute offset
std::size_t attribute_offset = 0;
// Define vertex position attribute
gl::vertex_attribute position_attribute;
position_attribute.buffer = m_vbo.get();
position_attribute.offset = attribute_offset;
position_attribute.stride = m_vertex_stride;
position_attribute.type = gl::vertex_attribute_type::float_32;
position_attribute.components = 3;
attribute_offset += position_attribute.components * sizeof(float);
// Define vertex UV attribute
gl::vertex_attribute uv_attribute;
uv_attribute.buffer = m_vbo.get();
uv_attribute.offset = attribute_offset;
uv_attribute.stride = m_vertex_stride;
uv_attribute.type = gl::vertex_attribute_type::float_32;
uv_attribute.components = 2;
attribute_offset += uv_attribute.components * sizeof(float);
// Define vertex color attribute
gl::vertex_attribute color_attribute;
color_attribute.buffer = m_vbo.get();
color_attribute.offset = attribute_offset;
color_attribute.stride = m_vertex_stride;
color_attribute.type = gl::vertex_attribute_type::float_32;
color_attribute.components = 4;
//attribute_offset += color_attribute.components * sizeof(float);
// Bind vertex attributes to VAO
m_vao->bind(render::vertex_attribute::position, position_attribute);
m_vao->bind(render::vertex_attribute::uv, uv_attribute);
m_vao->bind(render::vertex_attribute::color, color_attribute);
// Init render operation
m_render_op.vertex_array = m_vao.get();
m_render_op.drawing_mode = gl::drawing_mode::triangles;
}
void text::render(render::context& ctx) const
{
if (m_vertex_count)
{
m_render_op.depth = ctx.camera->get_view_frustum().near().distance(get_translation());
m_render_op.layer_mask = get_layer_mask();
ctx.operations.push_back(&m_render_op);
}
}
void text::refresh()
{
update_content();
}
void text::set_material(std::shared_ptr<render::material> material)
{
m_render_op.material = material;
}
void text::set_font(const type::bitmap_font* font)
{
if (m_font != font)
{
m_font = font;
update_content();
}
}
void text::set_direction(type::text_direction direction)
{
if (m_direction != direction)
{
m_direction = direction;
update_content();
}
}
void text::set_content(const std::string& content)
{
if (m_content_u8 != content)
{
m_content_u8 = content;
m_content_u32 = type::unicode::u32(m_content_u8);
update_content();
}
}
void text::set_color(const math::fvec4& color)
{
m_color = color;
update_color();
}
void text::transformed()
{
// Naive algorithm: transform each corner of the AABB
m_world_bounds = {math::fvec3::infinity(), -math::fvec3::infinity()};
for (std::size_t i = 0; i < 8; ++i)
{
m_world_bounds.extend(get_transform() * m_local_bounds.corner(i));
}
m_render_op.transform = get_transform().matrix();
}
void text::update_content()
{
// If no valid font or no text, clear vertex count
if (!m_font || m_content_u32.empty())
{
m_vertex_count = 0;
m_render_op.index_count = 0;
m_local_bounds = {{0, 0, 0}, {0, 0, 0}};
transformed();
return;
}
// Calculate new vertex count and minimum vertex buffer size
std::size_t vertex_count = m_content_u32.length() * 6;
std::size_t min_vertex_buffer_size = vertex_count * m_vertex_stride;
// Expand vertex data buffer to accommodate vertices
if (m_vertex_data.size() < min_vertex_buffer_size)
{
m_vertex_data.resize(min_vertex_buffer_size);
}
// Get font metrics and bitmap
const type::font_metrics& font_metrics = m_font->get_font_metrics();
const image& font_bitmap = m_font->get_bitmap();
// Init pen position
math::fvec2 pen_position = {0.0f, 0.0f};
// Reset local-space bounds
m_local_bounds.min = {std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity(), 0.0f};
m_local_bounds.max = {-std::numeric_limits<float>::infinity(), -std::numeric_limits<float>::infinity(), 0.0f};
// Generate vertex data
char32_t previous_code = 0;
float* v = reinterpret_cast<float*>(m_vertex_data.data());
for (char32_t code: m_content_u32)
{
// Apply kerning
if (previous_code)
{
pen_position.x() += m_font->get_kerning(previous_code, code).x();
}
if (m_font->contains(code))
{
// Get glyph
const type::bitmap_glyph& glyph = m_font->get_glyph(code);
// Calculate vertex positions
math::fvec2 positions[6];
positions[0] = pen_position + glyph.metrics.horizontal_bearing;
positions[1] = {positions[0].x(), positions[0].y() - glyph.metrics.height};
positions[2] = {positions[0].x() + glyph.metrics.width, positions[1].y()};
positions[3] = {positions[2].x(), positions[0].y()};
positions[4] = positions[0];
positions[5] = positions[2];
// Calculate vertex UVs
math::fvec2 uvs[6];
uvs[0] = {static_cast<float>(glyph.position.x()), static_cast<float>(glyph.position.y())};
uvs[1] = {uvs[0].x(), uvs[0].y() + glyph.metrics.height};
uvs[2] = {uvs[0].x() + glyph.metrics.width, uvs[1].y()};
uvs[3] = {uvs[2].x(), uvs[0].y()};
uvs[4] = uvs[0];
uvs[5] = uvs[2];
for (int i = 0; i < 6; ++i)
{
// Round positions
positions[i].x() = std::round(positions[i].x());
positions[i].y() = std::round(positions[i].y());
// Normalize UVs
uvs[i].x() = uvs[i].x() / static_cast<float>(font_bitmap.size().x());
uvs[i].y() = uvs[i].y() / static_cast<float>(font_bitmap.size().y());
}
// Add vertex to vertex data buffer
for (int i = 0; i < 6; ++i)
{
*(v++) = positions[i].x();
*(v++) = positions[i].y();
*(v++) = 0.0f;
*(v++) = uvs[i].x();
*(v++) = uvs[i].y();
*(v++) = m_color[0];
*(v++) = m_color[1];
*(v++) = m_color[2];
*(v++) = m_color[3];
}
// Advance pen position
pen_position.x() += glyph.metrics.horizontal_advance;
// Update local-space bounds
for (int i = 0; i < 4; ++i)
{
const math::fvec2& position = positions[i];
for (int j = 0; j < 2; ++j)
{
m_local_bounds.min[j] = std::min<float>(m_local_bounds.min[j], position[j]);
m_local_bounds.max[j] = std::max<float>(m_local_bounds.max[j], position[j]);
}
}
}
else
{
// Glyph not in font, zero vertex data
for (std::size_t i = 0; i < (6 * 9); ++i)
{
*(v++) = 0.0f;
}
}
// Handle newlines
if (code == static_cast<char32_t>('\n'))
{
pen_position.x() = 0.0f;
pen_position.y() -= font_metrics.linegap;
}
// Update previous UTF-32 character code
previous_code = code;
}
// Resize VBO, if necessary, and upload vertex data
if (vertex_count > m_vertex_count)
{
m_vbo->resize(min_vertex_buffer_size, m_vertex_data);
}
else
{
m_vbo->write({m_vertex_data.data(), min_vertex_buffer_size});
}
// Update vertex count
m_vertex_count = vertex_count;
m_render_op.index_count = vertex_count;
// Update world-space bounds
transformed();
}
void text::update_color()
{
float* v = reinterpret_cast<float*>(m_vertex_data.data());
for (std::size_t i = 0; i < m_vertex_count; ++i)
{
// Skip vertex position (vec3) and vertex UV (vec2)
v += (3 + 2);
// Update vertex color
*(v++) = m_color[0];
*(v++) = m_color[1];
*(v++) = m_color[2];
*(v++) = m_color[3];
}
// Update VBO
m_vbo->write({m_vertex_data.data(), m_vertex_count * m_vertex_stride});
}
} // namespace scene