|
|
@ -0,0 +1,314 @@ |
|
|
|
/*
|
|
|
|
* Copyright (C) 2021 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 "scene/text.hpp"
|
|
|
|
#include "renderer/material.hpp"
|
|
|
|
#include "renderer/vertex-attribute.hpp"
|
|
|
|
#include "math/vector-operators.hpp"
|
|
|
|
#include <cstddef>
|
|
|
|
#include <codecvt>
|
|
|
|
|
|
|
|
namespace scene { |
|
|
|
|
|
|
|
text::text(): |
|
|
|
local_bounds{{0, 0, 0}, {0, 0, 0}}, |
|
|
|
world_bounds{{0, 0, 0}, {0, 0, 0}}, |
|
|
|
material(nullptr), |
|
|
|
font(nullptr), |
|
|
|
direction(type::text_direction::ltr), |
|
|
|
content_u8(std::string()), |
|
|
|
content_u32(std::u32string()), |
|
|
|
color({0.0f, 0.0f, 0.0f, 1.0f}), |
|
|
|
vertex_stride(0), |
|
|
|
vertex_count(0), |
|
|
|
vao(nullptr), |
|
|
|
vbo(nullptr) |
|
|
|
{ |
|
|
|
// Allocate VBO and VAO
|
|
|
|
vbo = new gl::vertex_buffer(0, nullptr, gl::buffer_usage::static_draw); |
|
|
|
vao = new gl::vertex_array(); |
|
|
|
|
|
|
|
// Calculate vertex stride
|
|
|
|
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 = vbo; |
|
|
|
position_attribute.offset = attribute_offset; |
|
|
|
position_attribute.stride = 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 = vbo; |
|
|
|
uv_attribute.offset = attribute_offset; |
|
|
|
uv_attribute.stride = 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 = vbo; |
|
|
|
color_attribute.offset = attribute_offset; |
|
|
|
color_attribute.stride = 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
|
|
|
|
vao->bind(render::vertex_attribute::position, position_attribute); |
|
|
|
vao->bind(render::vertex_attribute::uv, uv_attribute); |
|
|
|
vao->bind(render::vertex_attribute::color, color_attribute); |
|
|
|
} |
|
|
|
|
|
|
|
text::~text() |
|
|
|
{ |
|
|
|
// Free VAO and VBO
|
|
|
|
delete vao; |
|
|
|
delete vbo; |
|
|
|
} |
|
|
|
|
|
|
|
void text::set_material(::material* material) |
|
|
|
{ |
|
|
|
this->material = material; |
|
|
|
} |
|
|
|
|
|
|
|
void text::set_font(const type::bitmap_font* font) |
|
|
|
{ |
|
|
|
if (this->font != font) |
|
|
|
{ |
|
|
|
this->font = font; |
|
|
|
|
|
|
|
// Update text in VBO
|
|
|
|
update_content(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void text::set_direction(type::text_direction direction) |
|
|
|
{ |
|
|
|
if (this->direction != direction) |
|
|
|
{ |
|
|
|
this->direction = direction; |
|
|
|
|
|
|
|
// Update text in VBO
|
|
|
|
update_content(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void text::set_content(const std::string& content) |
|
|
|
{ |
|
|
|
// If content has changed
|
|
|
|
if (content_u8 != content) |
|
|
|
{ |
|
|
|
// Update UTF-8 content
|
|
|
|
content_u8 = content; |
|
|
|
|
|
|
|
// Convert UTF-8 content to UTF-32
|
|
|
|
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert; |
|
|
|
content_u32 = convert.from_bytes(content_u8.c_str()); |
|
|
|
|
|
|
|
// Update text in VBO
|
|
|
|
update_content(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void text::set_color(const float4& color) |
|
|
|
{ |
|
|
|
this->color = color; |
|
|
|
|
|
|
|
// Update color in VBO
|
|
|
|
update_color(); |
|
|
|
} |
|
|
|
|
|
|
|
void text::transformed() |
|
|
|
{ |
|
|
|
world_bounds = aabb_type::transform(local_bounds, get_transform()); |
|
|
|
} |
|
|
|
|
|
|
|
void text::update_tweens() |
|
|
|
{ |
|
|
|
object_base::update_tweens(); |
|
|
|
if (material) |
|
|
|
{ |
|
|
|
material->update_tweens(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void text::update_content() |
|
|
|
{ |
|
|
|
// If no valid font or no text, clear vertex count
|
|
|
|
if (!font || content_u32.empty()) |
|
|
|
{ |
|
|
|
vertex_count = 0; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Calculate new vertex count and minimum vertex buffer size
|
|
|
|
std::size_t vertex_count = content_u32.length() * 6; |
|
|
|
std::size_t min_vertex_buffer_size = vertex_count * vertex_stride; |
|
|
|
|
|
|
|
// Expand vertex data buffer to accommodate vertices
|
|
|
|
if (vertex_data.size() < min_vertex_buffer_size) |
|
|
|
vertex_data.resize(min_vertex_buffer_size); |
|
|
|
|
|
|
|
// Get font metrics and bitmap
|
|
|
|
const type::font_metrics& font_metrics = font->get_font_metrics(); |
|
|
|
const image& font_bitmap = font->get_bitmap(); |
|
|
|
|
|
|
|
// Init pen position
|
|
|
|
float2 pen_position = {0.0f, 0.0f}; |
|
|
|
|
|
|
|
// Reset local-space bounds
|
|
|
|
local_bounds = {{0, 0, 0}, {0, 0, 0}}; |
|
|
|
|
|
|
|
// Generate vertex data
|
|
|
|
char32_t previous_code = 0; |
|
|
|
float* v = reinterpret_cast<float*>(vertex_data.data()); |
|
|
|
for (char32_t code: content_u32) |
|
|
|
{ |
|
|
|
// Apply kerning
|
|
|
|
if (previous_code) |
|
|
|
{ |
|
|
|
pen_position.x += font->get_kerning(previous_code, code).x; |
|
|
|
} |
|
|
|
|
|
|
|
if (font->contains(code)) |
|
|
|
{ |
|
|
|
// Get glyph
|
|
|
|
const type::bitmap_glyph& glyph = font->get_glyph(code); |
|
|
|
|
|
|
|
// Calculate vertex positions
|
|
|
|
float2 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
|
|
|
|
float2 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.get_width()); |
|
|
|
uvs[i].y = uvs[i].y / static_cast<float>(font_bitmap.get_height()); |
|
|
|
} |
|
|
|
|
|
|
|
// 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++) = color.x; |
|
|
|
*(v++) = color.y; |
|
|
|
*(v++) = color.z; |
|
|
|
*(v++) = color.w; |
|
|
|
} |
|
|
|
|
|
|
|
// Advance pen position
|
|
|
|
pen_position.x += glyph.metrics.horizontal_advance; |
|
|
|
|
|
|
|
// Update local-space bounds
|
|
|
|
for (int i = 0; i < 4; ++i) |
|
|
|
{ |
|
|
|
const float2& position = positions[i]; |
|
|
|
for (int j = 0; j < 2; ++j) |
|
|
|
{ |
|
|
|
local_bounds.min_point[j] = std::min<float>(local_bounds.min_point[j], position[j]); |
|
|
|
local_bounds.max_point[j] = std::max<float>(local_bounds.max_point[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 > this->vertex_count) |
|
|
|
{ |
|
|
|
this->vertex_count = vertex_count; |
|
|
|
vbo->resize(min_vertex_buffer_size, vertex_data.data()); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
vbo->write(0, min_vertex_buffer_size, vertex_data.data()); |
|
|
|
} |
|
|
|
|
|
|
|
// Update vertex count
|
|
|
|
this->vertex_count = vertex_count; |
|
|
|
|
|
|
|
// Update world-space bounds
|
|
|
|
transformed(); |
|
|
|
} |
|
|
|
|
|
|
|
void text::update_color() |
|
|
|
{ |
|
|
|
float* v = reinterpret_cast<float*>(vertex_data.data()); |
|
|
|
for (std::size_t i = 0; i < vertex_count; ++i) |
|
|
|
{ |
|
|
|
// Skip vertex position (vec3) and vertex UV (vec2)
|
|
|
|
v += (3 + 2); |
|
|
|
|
|
|
|
// Update vertex color
|
|
|
|
*(v++) = color.x; |
|
|
|
*(v++) = color.y; |
|
|
|
*(v++) = color.z; |
|
|
|
*(v++) = color.w; |
|
|
|
} |
|
|
|
|
|
|
|
// Update VBO
|
|
|
|
vbo->write(0, vertex_count * vertex_stride, vertex_data.data()); |
|
|
|
} |
|
|
|
|
|
|
|
} // namespace scene
|