rewrite label_system

This commit is contained in:
2019-08-21 07:53:24 +07:00
parent b7b2b5889f
commit c29c1643f0

View File

@@ -6,59 +6,352 @@
#include <enduro2d/high/systems/label_system.hpp> #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/label.hpp>
#include <enduro2d/high/components/renderer.hpp> #include <enduro2d/high/components/renderer.hpp>
#include <enduro2d/high/components/model_renderer.hpp> #include <enduro2d/high/components/model_renderer.hpp>
#include <enduro2d/high/assets/texture_asset.hpp>
#include <enduro2d/high/assets/shader_asset.hpp>
namespace namespace
{ {
using namespace e2d; using namespace e2d;
f32 get_x_pos(const label& l, f32 string_width) { class geometry_builder {
f32 x_pos{0}; public:
switch ( l.halign() ) { geometry_builder() = default;
case label::haligns::left :
x_pos = 0; void add_quad(
break; const b2f& rect,
case label::haligns::center : const b2f& texrect,
x_pos = -0.5f * string_width; const color32& color)
break; {
case label::haligns::right : const std::size_t start_vertex = vertices_.size();
x_pos = -string_width;
break; // Y
default: // ^
E2D_ASSERT_MSG(false,"label_system: uncknow horizontal align flag"); // | 3 - 2
break; // | | / |
// | 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) { f32 calculate_valign_offset(
const auto& f = l.font()->content(); label::valigns valign,
f32 label_height = f.info().line_height * strings_count; f32 leading,
f32 top{0}; u32 glyph_ascent,
switch ( l.valign() ) { u32 line_height,
case label::valigns::top: std::size_t string_count) noexcept
top = label_height; {
break; const f32 label_height = string_count > 1u
case label::valigns::center: ? line_height + line_height * leading * (string_count - 1u)
top = 0.5f * label_height; : line_height;
break;
case label::valigns::bottom: switch ( valign ) {
top = 0; case label::valigns::top:
break; return 0.f;
case label::valigns::baseline: case label::valigns::center:
top = label_height - (f.info().line_height - f.info().glyph_ascent); return 0.5f * label_height;
break; case label::valigns::bottom:
default: return 1.0f * label_height;
E2D_ASSERT_MSG(false,"label_system: uncknow vertical align flag"); case label::valigns::baseline:
break; 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; ~internal_state() noexcept = default;
void process(ecs::registry& owner) { void process(ecs::registry& owner) {
owner.for_joined_components<label::dirty, label, renderer, model_renderer>([]( update_dirty_labels(owner);
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>();
} }
}; };