mirror of
https://github.com/enduro2d/enduro2d.git
synced 2025-12-15 00:11:55 +07:00
rewrite label_system
This commit is contained in:
@@ -6,59 +6,352 @@
|
||||
|
||||
#include <enduro2d/high/systems/label_system.hpp>
|
||||
|
||||
#include <enduro2d/high/assets/font_asset.hpp>
|
||||
#include <enduro2d/high/assets/texture_asset.hpp>
|
||||
|
||||
#include <enduro2d/high/components/label.hpp>
|
||||
#include <enduro2d/high/components/renderer.hpp>
|
||||
#include <enduro2d/high/components/model_renderer.hpp>
|
||||
#include <enduro2d/high/assets/texture_asset.hpp>
|
||||
#include <enduro2d/high/assets/shader_asset.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace e2d;
|
||||
|
||||
f32 get_x_pos(const label& l, f32 string_width) {
|
||||
f32 x_pos{0};
|
||||
switch ( l.halign() ) {
|
||||
case label::haligns::left :
|
||||
x_pos = 0;
|
||||
break;
|
||||
case label::haligns::center :
|
||||
x_pos = -0.5f * string_width;
|
||||
break;
|
||||
case label::haligns::right :
|
||||
x_pos = -string_width;
|
||||
break;
|
||||
default:
|
||||
E2D_ASSERT_MSG(false,"label_system: uncknow horizontal align flag");
|
||||
break;
|
||||
class geometry_builder {
|
||||
public:
|
||||
geometry_builder() = default;
|
||||
|
||||
void add_quad(
|
||||
const b2f& rect,
|
||||
const b2f& texrect,
|
||||
const color32& color)
|
||||
{
|
||||
const std::size_t start_vertex = vertices_.size();
|
||||
|
||||
// Y
|
||||
// ^
|
||||
// | 3 - 2
|
||||
// | | / |
|
||||
// | 0 - 1
|
||||
// +------> X
|
||||
|
||||
{
|
||||
const v2f& gp = rect.position;
|
||||
const v2f& gs = rect.size;
|
||||
|
||||
vertices_.emplace_back(gp.x + 0.0f, gp.y + 0.0f, 0.f);
|
||||
vertices_.emplace_back(gp.x + gs.x, gp.y + 0.0f, 0.f);
|
||||
vertices_.emplace_back(gp.x + gs.x, gp.y + gs.y, 0.f);
|
||||
vertices_.emplace_back(gp.x + 0.0f, gp.y + gs.y, 0.f);
|
||||
}
|
||||
|
||||
{
|
||||
const v2f& tp = texrect.position;
|
||||
const v2f& ts = texrect.size;
|
||||
|
||||
uvs_.emplace_back(tp.x + 0.0f, tp.y + 0.0f);
|
||||
uvs_.emplace_back(tp.x + ts.x, tp.y + 0.0f);
|
||||
uvs_.emplace_back(tp.x + ts.x, tp.y + ts.y);
|
||||
uvs_.emplace_back(tp.x + 0.0f, tp.y + ts.y);
|
||||
}
|
||||
|
||||
{
|
||||
indices_.emplace_back(start_vertex + 0u);
|
||||
indices_.emplace_back(start_vertex + 1u);
|
||||
indices_.emplace_back(start_vertex + 2u);
|
||||
indices_.emplace_back(start_vertex + 2u);
|
||||
indices_.emplace_back(start_vertex + 3u);
|
||||
indices_.emplace_back(start_vertex + 0u);
|
||||
}
|
||||
|
||||
{
|
||||
colors_.emplace_back(color);
|
||||
colors_.emplace_back(color);
|
||||
colors_.emplace_back(color);
|
||||
colors_.emplace_back(color);
|
||||
}
|
||||
}
|
||||
|
||||
return x_pos;
|
||||
void update_model(model_renderer& mr) {
|
||||
mesh new_mesh;
|
||||
new_mesh.set_vertices(vertices_);
|
||||
new_mesh.set_indices(0, indices_);
|
||||
new_mesh.set_uvs(0, uvs_);
|
||||
new_mesh.set_colors(0, colors_);
|
||||
|
||||
model new_model;
|
||||
new_model.set_mesh(mesh_asset::create(std::move(new_mesh)));
|
||||
new_model.regenerate_geometry(the<render>());
|
||||
|
||||
if ( mr.model() ) {
|
||||
mr.model()->fill(std::move(new_model));
|
||||
} else {
|
||||
mr.model(model_asset::create(std::move(new_model)));
|
||||
}
|
||||
}
|
||||
|
||||
void clear() noexcept {
|
||||
vertices_.clear();
|
||||
uvs_.clear();
|
||||
indices_.clear();
|
||||
colors_.clear();
|
||||
}
|
||||
private:
|
||||
vector<v3f> vertices_;
|
||||
vector<v2f> uvs_;
|
||||
vector<u32> indices_;
|
||||
vector<color32> colors_;
|
||||
};
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace e2d;
|
||||
|
||||
f32 calculate_halign_offset(
|
||||
label::haligns halign,
|
||||
f32 string_width) noexcept
|
||||
{
|
||||
switch ( halign ) {
|
||||
case label::haligns::left:
|
||||
return 0.0f;
|
||||
case label::haligns::center:
|
||||
return -0.5f * string_width;
|
||||
case label::haligns::right:
|
||||
return -1.0f * string_width;
|
||||
default:
|
||||
E2D_ASSERT_MSG(false, "unexpected label halign");
|
||||
return 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
f32 get_top_pos(const label& l, u32 strings_count) {
|
||||
const auto& f = l.font()->content();
|
||||
f32 label_height = f.info().line_height * strings_count;
|
||||
f32 top{0};
|
||||
switch ( l.valign() ) {
|
||||
case label::valigns::top:
|
||||
top = label_height;
|
||||
break;
|
||||
case label::valigns::center:
|
||||
top = 0.5f * label_height;
|
||||
break;
|
||||
case label::valigns::bottom:
|
||||
top = 0;
|
||||
break;
|
||||
case label::valigns::baseline:
|
||||
top = label_height - (f.info().line_height - f.info().glyph_ascent);
|
||||
break;
|
||||
default:
|
||||
E2D_ASSERT_MSG(false,"label_system: uncknow vertical align flag");
|
||||
break;
|
||||
f32 calculate_valign_offset(
|
||||
label::valigns valign,
|
||||
f32 leading,
|
||||
u32 glyph_ascent,
|
||||
u32 line_height,
|
||||
std::size_t string_count) noexcept
|
||||
{
|
||||
const f32 label_height = string_count > 1u
|
||||
? line_height + line_height * leading * (string_count - 1u)
|
||||
: line_height;
|
||||
|
||||
switch ( valign ) {
|
||||
case label::valigns::top:
|
||||
return 0.f;
|
||||
case label::valigns::center:
|
||||
return 0.5f * label_height;
|
||||
case label::valigns::bottom:
|
||||
return 1.0f * label_height;
|
||||
case label::valigns::baseline:
|
||||
return 1.0f * glyph_ascent;
|
||||
default:
|
||||
E2D_ASSERT_MSG(false, "unexpected label valign");
|
||||
return 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t calculate_string_count(str32_view text) noexcept {
|
||||
std::size_t count{0u};
|
||||
for ( std::size_t i = 0; i < text.size(); ++i ) {
|
||||
if ( text[i] == '\n' ) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace e2d;
|
||||
|
||||
void update_label_material(const label& l, renderer& r) {
|
||||
if ( r.materials().empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return top;
|
||||
auto texture_p = the<library>().load_asset<texture_asset>(
|
||||
l.font()->content().info().atlas_file);
|
||||
|
||||
const f32 glyph_dilate = math::clamp(l.glyph_dilate(), -1.f, 1.0f);
|
||||
const f32 outline_width = math::clamp(l.outline_width(), 0.f, 1.f - glyph_dilate);
|
||||
const v4f outline_color = make_vec4(color(l.outline_color()));
|
||||
|
||||
r.properties(render::property_block()
|
||||
.sampler("u_texture", render::sampler_state()
|
||||
.texture(texture_p->content())
|
||||
.min_filter(render::sampler_min_filter::linear)
|
||||
.mag_filter(render::sampler_mag_filter::linear))
|
||||
.property("u_glyph_dilate", glyph_dilate)
|
||||
.property("u_outline_width", outline_width)
|
||||
.property("u_outline_color", outline_color));
|
||||
}
|
||||
|
||||
void update_label_geometry(const label& l, model_renderer& mr, geometry_builder& gb) {
|
||||
if ( !l.font() || l.font()->content().empty() || l.text().empty() ) {
|
||||
gb.update_model(mr);
|
||||
return;
|
||||
}
|
||||
|
||||
const str32& text = l.text();
|
||||
const font& f = l.font()->content();
|
||||
|
||||
//
|
||||
// update glyphs
|
||||
//
|
||||
|
||||
struct glyph_desc {
|
||||
const font::glyph_info* glyph{nullptr};
|
||||
f32 kerning{0.f};
|
||||
};
|
||||
|
||||
vector<glyph_desc> glyphs;
|
||||
glyphs.reserve(text.size());
|
||||
|
||||
for ( std::size_t i = 0, e = text.size(); i < e; ++i ) {
|
||||
glyph_desc desc;
|
||||
desc.glyph = f.find_glyph(text[i]);
|
||||
desc.kerning = i > 0
|
||||
? f.get_kerning(text[i-1], text[i])
|
||||
: 0.f;
|
||||
glyphs.push_back(std::move(desc));
|
||||
|
||||
if ( !desc.glyph && text[i] != '\n' ) {
|
||||
the<debug>().warning("LABEL: Missing font glyph:\n"
|
||||
"--> Code Point: %0",
|
||||
text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
f32 tracking_width = 0.f;
|
||||
if ( const font::glyph_info* sp = f.find_glyph(' '); sp ) {
|
||||
tracking_width = sp->advance * l.tracking();
|
||||
}
|
||||
|
||||
//
|
||||
// update strings
|
||||
//
|
||||
|
||||
struct string_desc {
|
||||
f32 width{0.f};
|
||||
std::size_t start{0};
|
||||
std::size_t length{0};
|
||||
|
||||
string_desc(std::size_t start)
|
||||
: start(start) {}
|
||||
};
|
||||
|
||||
vector<string_desc> strings;
|
||||
strings.reserve(calculate_string_count(text));
|
||||
|
||||
f32 last_space_width = 0.f;
|
||||
std::size_t last_space_index = std::size_t(-1);
|
||||
|
||||
strings.push_back(string_desc(0u));
|
||||
|
||||
for ( std::size_t i = 0, e = text.size(); i < e; ++i ) {
|
||||
const u32 code_point = text[i];
|
||||
const glyph_desc& glyph = glyphs[i];
|
||||
|
||||
if ( code_point == ' ' ) {
|
||||
last_space_width = strings.back().width;
|
||||
last_space_index = i;
|
||||
}
|
||||
|
||||
bool new_line = false;
|
||||
|
||||
if ( code_point == '\n' ) {
|
||||
new_line = true;
|
||||
strings.back().length = i - strings.back().start;
|
||||
} else if ( glyph.glyph ) {
|
||||
strings.back().width +=
|
||||
glyph.kerning +
|
||||
glyph.glyph->advance +
|
||||
tracking_width;
|
||||
|
||||
const bool break_line =
|
||||
l.text_width() > 0.f &&
|
||||
last_space_index != std::size_t(-1) &&
|
||||
strings.back().width > l.text_width();
|
||||
|
||||
if ( break_line ) {
|
||||
new_line = true;
|
||||
strings.back().width = last_space_width;
|
||||
strings.back().length = last_space_index - strings.back().start;
|
||||
i = last_space_index;
|
||||
}
|
||||
}
|
||||
|
||||
if ( i == e - 1 ) {
|
||||
new_line = true;
|
||||
strings.back().length = i + 1 - strings.back().start;
|
||||
}
|
||||
|
||||
if ( new_line ) {
|
||||
if ( i < e - 1 ) {
|
||||
strings.push_back(string_desc(i + 1u));
|
||||
}
|
||||
last_space_width = 0.f;
|
||||
last_space_index = std::size_t(-1);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// update geometry
|
||||
//
|
||||
|
||||
v2f cursor = v2f::unit_y() * calculate_valign_offset(
|
||||
l.valign(),
|
||||
l.leading(),
|
||||
f.info().glyph_ascent,
|
||||
f.info().line_height,
|
||||
strings.size());
|
||||
|
||||
for ( std::size_t i = 0, ie = strings.size(); i < ie; ++i ) {
|
||||
cursor.x = calculate_halign_offset(l.halign(), strings[i].width);
|
||||
for ( std::size_t j = strings[i].start, je = strings[i].start + strings[i].length; j < je; ++j ) {
|
||||
const glyph_desc& glyph = glyphs[j];
|
||||
if ( !glyph.glyph ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cursor.x += glyph.kerning;
|
||||
|
||||
b2f rect = make_rect(cursor, glyph.glyph->tex_rect.size.cast_to<f32>());
|
||||
rect.position += glyph.glyph->offset.cast_to<f32>();
|
||||
|
||||
b2f texrect = glyph.glyph->tex_rect.cast_to<f32>();
|
||||
texrect.position /= f.info().atlas_size.cast_to<f32>();
|
||||
texrect.size /= f.info().atlas_size.cast_to<f32>();
|
||||
|
||||
gb.add_quad(rect, texrect, l.tint());
|
||||
cursor.x += glyph.glyph->advance + tracking_width;
|
||||
}
|
||||
cursor.y -= f.info().line_height * l.leading();
|
||||
}
|
||||
|
||||
//
|
||||
// update model
|
||||
//
|
||||
|
||||
gb.update_model(mr);
|
||||
}
|
||||
|
||||
void update_dirty_labels(ecs::registry& owner) {
|
||||
geometry_builder gb;
|
||||
owner.for_joined_components<label::dirty, label, renderer, model_renderer>([&gb](
|
||||
const ecs::const_entity&,
|
||||
const label::dirty&,
|
||||
const label& l,
|
||||
renderer& r,
|
||||
model_renderer& mr
|
||||
){
|
||||
update_label_material(l, r);
|
||||
update_label_geometry(l, mr, gb);
|
||||
gb.clear();
|
||||
});
|
||||
owner.remove_all_components<label::dirty>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,222 +367,7 @@ namespace e2d
|
||||
~internal_state() noexcept = default;
|
||||
|
||||
void process(ecs::registry& owner) {
|
||||
owner.for_joined_components<label::dirty, label, renderer, model_renderer>([](
|
||||
const ecs::const_entity&,
|
||||
const label::dirty&,
|
||||
const label& l,
|
||||
renderer& r,
|
||||
model_renderer& mr)
|
||||
{
|
||||
vector<v3f> vertices;
|
||||
vector<u32> indices;
|
||||
vector<v2f> uvs;
|
||||
vector<v3f> normals;
|
||||
vector<color32> colors;
|
||||
v2f offset;
|
||||
f32 kerning{0};
|
||||
u32 prev_char{0};
|
||||
str32 text = l.text();
|
||||
if (text.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto& f = l.font()->content();
|
||||
v2f texture_size = f.info().atlas_size.cast_to<f32>();
|
||||
|
||||
if ( l.width() != 0 ) {
|
||||
f32 word_width{0};
|
||||
f32 string_width{0};
|
||||
i32 last_space{-1};
|
||||
for ( size_t i = 0; i < text.size(); i++ ) {
|
||||
const auto& ch = text[i];
|
||||
if ( ch == '\n' ) {
|
||||
word_width = 0;
|
||||
string_width = 0;
|
||||
last_space = -1;
|
||||
} else {
|
||||
const font::glyph_info* glyph = f.find_glyph(ch);
|
||||
if ( glyph ) {
|
||||
prev_char != 0
|
||||
? kerning = f.get_kerning(prev_char, ch)
|
||||
: kerning = 0;
|
||||
prev_char = ch;
|
||||
f32 char_width = glyph->advance + kerning;
|
||||
if ( ch == ' ' ) {
|
||||
if ( string_width + word_width < l.width() ) {
|
||||
if ( string_width + word_width + char_width < l.width() ) {
|
||||
string_width += word_width + char_width;
|
||||
word_width = 0;
|
||||
last_space = i;
|
||||
} else {
|
||||
text[i] = '\n';
|
||||
word_width = 0;
|
||||
string_width = 0;
|
||||
last_space = -1;
|
||||
}
|
||||
} else {
|
||||
if ( last_space != -1 ) {
|
||||
text[last_space] = '\n';
|
||||
last_space = -1;
|
||||
string_width = 0;
|
||||
--i;
|
||||
} else {
|
||||
text[i] = '\n';
|
||||
word_width = 0;
|
||||
string_width = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
word_width += char_width;
|
||||
if ( i == text.size() - 1) {
|
||||
if ( string_width + word_width > l.width() ) {
|
||||
if ( last_space != -1 ) {
|
||||
text[last_space] = '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t strings_count{1};
|
||||
for ( size_t i = 0; i < text.size(); i++ ) {
|
||||
if ( text[i] == '\n' && i != text.size() - 1) {
|
||||
strings_count++;
|
||||
}
|
||||
}
|
||||
size_t letters_size{0};
|
||||
vector<f32> strings_width(strings_count);
|
||||
std::fill(strings_width.begin(), strings_width.end(), 0);
|
||||
prev_char = 0;
|
||||
for ( size_t i = 0, string_ind = 0; i < text.size(); i++ ) {
|
||||
if ( text[i] != '\n' ) {
|
||||
const font::glyph_info* glyph = f.find_glyph(text[i]);
|
||||
if ( glyph ) {
|
||||
letters_size++;
|
||||
prev_char != 0
|
||||
? kerning = f.get_kerning(prev_char, text[i])
|
||||
: kerning = 0;
|
||||
strings_width[string_ind] += glyph->advance + kerning;
|
||||
}
|
||||
} else {
|
||||
string_ind++;
|
||||
}
|
||||
}
|
||||
|
||||
vertices.resize(letters_size * 4);
|
||||
uvs.resize(vertices.size());
|
||||
colors.resize(vertices.size());
|
||||
indices.resize(text.size() * 6);
|
||||
f32 x_pos = get_x_pos(l, strings_width[0]);
|
||||
f32 y_pos = get_top_pos(l, strings_count);
|
||||
u32 letters_counter{0};
|
||||
prev_char = 0;
|
||||
for ( size_t i = 0, str_ind = 0; i < text.size(); i++ ) {
|
||||
if ( text[i] == '\n' ) {
|
||||
str_ind++;
|
||||
y_pos -= f.info().line_height;
|
||||
x_pos = get_x_pos(l, strings_width[str_ind]);
|
||||
prev_char = 0;
|
||||
continue;
|
||||
}
|
||||
const font::glyph_info* glyph = f.find_glyph(text[i]);
|
||||
if ( glyph ) {
|
||||
offset = glyph->offset.cast_to<f32>();
|
||||
prev_char != 0
|
||||
? kerning = f.get_kerning(prev_char, text[i])
|
||||
: kerning = 0;
|
||||
offset.x += kerning;
|
||||
prev_char = text[i];
|
||||
size_t start_vertices = letters_counter * 4;
|
||||
vertices[start_vertices] = v3f(
|
||||
x_pos + offset.x,
|
||||
y_pos - offset.y - glyph->tex_rect.size.y,
|
||||
0);
|
||||
vertices[start_vertices + 1] = v3f(
|
||||
x_pos + offset.x,
|
||||
y_pos - offset.y,
|
||||
0);
|
||||
vertices[start_vertices + 2] = v3f(
|
||||
x_pos + glyph->tex_rect.size.x + offset.x,
|
||||
y_pos - offset.y,
|
||||
0);
|
||||
vertices[start_vertices + 3] = v3f(
|
||||
x_pos + glyph->tex_rect.size.x + offset.x,
|
||||
y_pos - offset.y - glyph->tex_rect.size.y,
|
||||
0);
|
||||
|
||||
uvs[start_vertices] = v2f(
|
||||
glyph->tex_rect.position.x / texture_size.x,
|
||||
glyph->tex_rect.position.y / texture_size.y);
|
||||
uvs[start_vertices + 1] = v2f(
|
||||
glyph->tex_rect.position.x / texture_size.x,
|
||||
(glyph->tex_rect.position.y + glyph->tex_rect.size.y) / texture_size.y);
|
||||
uvs[start_vertices + 2] = v2f(
|
||||
(glyph->tex_rect.position.x + glyph->tex_rect.size.x) / texture_size.x,
|
||||
(glyph->tex_rect.position.y + glyph->tex_rect.size.y) / texture_size.y);
|
||||
uvs[start_vertices + 3] = v2f(
|
||||
(glyph->tex_rect.position.x + glyph->tex_rect.size.x) / texture_size.x,
|
||||
glyph->tex_rect.position.y / texture_size.y);
|
||||
|
||||
colors[start_vertices] = l.tint();
|
||||
colors[start_vertices + 1] = l.tint();
|
||||
colors[start_vertices + 2] = l.tint();
|
||||
colors[start_vertices + 3] = l.tint();
|
||||
|
||||
size_t start_indices = i * 6;
|
||||
indices[start_indices] = start_vertices;
|
||||
indices[start_indices + 1] = start_vertices + 1;
|
||||
indices[start_indices + 2] = start_vertices + 2;
|
||||
indices[start_indices + 3] = start_vertices + 2;
|
||||
indices[start_indices + 4] = start_vertices + 3;
|
||||
indices[start_indices + 5] = start_vertices;
|
||||
|
||||
x_pos += glyph->advance + kerning;
|
||||
letters_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
mesh m;
|
||||
m.set_vertices(std::move(vertices));
|
||||
m.set_indices(0, std::move(indices));
|
||||
m.set_uvs(0, std::move(uvs));
|
||||
m.set_colors(0, std::move(colors));
|
||||
|
||||
model content;
|
||||
content.set_mesh(mesh_asset::create(m));
|
||||
content.regenerate_geometry(the<render>());
|
||||
|
||||
if ( !mr.model() ) {
|
||||
mr.model(model_asset::create(content));
|
||||
} else {
|
||||
mr.model()->fill(content);
|
||||
}
|
||||
|
||||
if ( r.materials().size() > 0 &&
|
||||
r.materials().front()->content().properties().sampler_count() == 0 ) {
|
||||
auto texture_p = the<library>().load_asset<texture_asset>(f.info().atlas_file);
|
||||
if ( !texture_p ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const f32 glyph_dilate = math::clamp(l.glyph_dilate(), -1.f, 1.0f);
|
||||
const f32 outline_width = math::clamp(l.outline_width(), 0.f, 1.f - glyph_dilate);
|
||||
const v4f outline_color = make_vec4(color(l.outline_color()));
|
||||
|
||||
r.properties(render::property_block()
|
||||
.sampler("u_texture", render::sampler_state()
|
||||
.texture(texture_p->content())
|
||||
.min_filter(render::sampler_min_filter::linear)
|
||||
.mag_filter(render::sampler_mag_filter::linear))
|
||||
.property("u_glyph_dilate", glyph_dilate)
|
||||
.property("u_outline_width", outline_width)
|
||||
.property("u_outline_color", outline_color));
|
||||
}
|
||||
});
|
||||
|
||||
owner.remove_all_components<label::dirty>();
|
||||
update_dirty_labels(owner);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user