diff --git a/sources/enduro2d/high/systems/label_system.cpp b/sources/enduro2d/high/systems/label_system.cpp index 17065466..8278fb5b 100644 --- a/sources/enduro2d/high/systems/label_system.cpp +++ b/sources/enduro2d/high/systems/label_system.cpp @@ -6,59 +6,352 @@ #include +#include +#include + #include #include #include -#include -#include 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()); + + 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 vertices_; + vector uvs_; + vector indices_; + vector 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().load_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 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().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 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()); + rect.position += glyph.glyph->offset.cast_to(); + + b2f texrect = glyph.glyph->tex_rect.cast_to(); + texrect.position /= f.info().atlas_size.cast_to(); + texrect.size /= f.info().atlas_size.cast_to(); + + 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([&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(); } } @@ -74,222 +367,7 @@ namespace e2d ~internal_state() noexcept = default; void process(ecs::registry& owner) { - owner.for_joined_components([]( - const ecs::const_entity&, - const label::dirty&, - const label& l, - renderer& r, - model_renderer& mr) - { - vector vertices; - vector indices; - vector uvs; - vector normals; - vector 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(); - - 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 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(); - 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()); - - 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().load_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(); + update_dirty_labels(owner); } };