#include "behavior.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../payload.h" #include "../behavior.h" #include "../manager/behavior.h" #define EXT_COLOR_FLOATS 1 namespace { struct Mesh { pod::Vector3f position; pod::Vector2f uv; #if EXT_COLOR_FLOATS pod::Vector4f color; #else pod::ColorRgba color; #endif static uf::stl::vector descriptor; }; } UF_VERTEX_DESCRIPTOR(Mesh, UF_VERTEX_DESCRIPTION(Mesh, R32G32B32_SFLOAT, position) UF_VERTEX_DESCRIPTION(Mesh, R32G32_SFLOAT, uv) #if EXT_COLOR_FLOATS UF_VERTEX_DESCRIPTION(Mesh, R32G32B32A32_SFLOAT, color) #else UF_VERTEX_DESCRIPTION(Mesh, R8G8B8A8_UNORM, color) #endif ) #include #include namespace { const uint64_t COLOR_CTRL = 0x7F; struct GlyphBox { struct { float x, y, w, h; } box; pod::Vector3f color; uint64_t code; }; struct { ext::freetype::Glyph glyph; uf::stl::unordered_map> cache; } glyphs; struct { uf::Serializer settings; pod::Vector2ui size = { 1920, 1080 }; } defaults; } UF_BEHAVIOR_REGISTER_CPP(ext::GuiGlyphBehavior) UF_BEHAVIOR_TRAITS_CPP(ext::GuiGlyphBehavior, ticks = true, renders = false, multithread = false) #define this (&self) namespace { size_t hashGlyphSettings( uint64_t c, const ext::GuiGlyphBehavior::Metadata& metadata ) { size_t seed{}; uf::hash( seed, c, metadata.padding[0], metadata.padding[1], metadata.spread, metadata.size, metadata.font, metadata.sdf ); return seed; } size_t hashGlyphSettings( const uf::stl::string& c, const ext::GuiGlyphBehavior::Metadata& metadata ) { size_t seed{}; uf::hash( seed, c, metadata.padding[0], metadata.padding[1], metadata.spread, metadata.size, metadata.font, metadata.sdf ); return seed; } uf::stl::vector<::GlyphBox> generateGlyphs( uf::Object& self, const uf::stl::string& _string ) { auto& glyphs = ::glyphs; auto& metadata = this->getComponent(); auto& metadataGui = this->getComponent(); auto& transform = this->getComponent>(); uf::stl::vector<::GlyphBox> gs; auto string = _string == "" ? metadata.string : _string; auto font = uf::io::root+"/fonts/" + metadata.font; auto color = metadataGui.color; auto origin = uf::stl::string("top"); auto align = uf::stl::string("center"); auto direction = uf::stl::string("down"); if ( glyphs.cache[font].empty() ) ext::freetype::initialize( glyphs.glyph, font ); struct { struct { float x = 0; float y = 0; } origin; struct { float x = 0; float y = 0; } cursor; struct { float sum = 0; float len = 0; float proc = 0; float tab = 4; } average; struct { float x = 0; float y = 0; } biggest; struct { float w = 0; float h = 0; } box; struct { uf::stl::vector container; size_t index = 0; } colors; } stat; stat.colors.container.push_back(color); // grab escaped color markers: ${#RRGGBB} { uf::stl::unordered_map colors; uf::stl::string text = string; std::regex regex("\\$\\{\\#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})\\}"); std::smatch match; bool matched = false; int maxTries = 128; while ( (matched = std::regex_search( text, match, regex )) && --maxTries > 0 ) { struct { uf::stl::string str; int dec; } r, g, b; r.str = match[1].str(); g.str = match[2].str(); b.str = match[3].str(); { uf::stl::stringstream stream; stream << r.str; stream >> std::hex >> r.dec; } { uf::stl::stringstream stream; stream << g.str; stream >> std::hex >> g.dec; } { uf::stl::stringstream stream; stream << b.str; stream >> std::hex >> b.dec; } pod::Vector3f color = { r.dec / 255.0f, g.dec / 255.0f, b.dec / 255.0f }; stat.colors.container.push_back(color); text = uf::string::replace( text, "${" + r.str + g.str + b.str + "}", "\x7F" ); } if ( maxTries == 0 ) text += "\n(error formatting)"; string = text; } std::wstring_convert, wchar_t> convert; std::wstring str = convert.from_bytes(string); if ( str.size() == 0 ) return gs; // Calculate statistics { // Find tallest glyph for new line for ( auto it = str.begin(); it != str.end(); ++it ) { uint64_t c = *it; if ( c == '\n' ) continue; if ( c == '\t' ) continue; if ( c == 0x01 ) continue; auto key = hashGlyphSettings( c, metadata ); auto& glyph = glyphs.cache[font][key]; if ( !glyph.generated() ) { glyph.setPadding( { metadata.padding[0], metadata.padding[1] } ); glyph.setSpread( metadata.spread ); #if UF_USE_VULKAN if ( metadata.sdf ) glyph.useSdf(true); #else glyph.useSdf(false); #endif glyph.generate( glyphs.glyph, c, metadata.size ); } stat.biggest.x = std::max( (float) stat.biggest.x, (float) glyph.getSize().x); stat.biggest.y = std::max( (float) stat.biggest.y, (float) glyph.getSize().y); stat.average.sum += glyph.getSize().x; ++stat.average.len; } stat.average.proc = stat.average.sum / stat.average.len; stat.average.tab *= stat.average.proc; // Calculate box: Second pass required because of tab stat.cursor.x = 0; stat.cursor.y = 0; stat.origin.x = 0; stat.origin.y = 0; for ( auto it = str.begin(); it != str.end(); ++it ) { uint64_t c = *it; if ( c == '\n' ) { stat.cursor.y += stat.biggest.y; stat.cursor.x = 0; continue; } else if ( c == '\t' ) { // Fixed movement vs Real Tabbing if ( false ) { stat.cursor.x += stat.average.tab; } else { stat.cursor.x = ((stat.cursor.x / stat.average.tab) + 1) * stat.average.tab; } continue; } else if ( c == COLOR_CTRL ) { continue; } auto key = hashGlyphSettings( c, metadata ); auto& glyph = glyphs.cache[font][key]; ::GlyphBox g; g.box.w = glyph.getSize().x; g.box.h = glyph.getSize().y; g.box.x = stat.cursor.x + glyph.getBearing().x; g.box.y = stat.cursor.y - glyph.getBearing().y; // - (glyph.getSize().y - glyph.getBearing().y); stat.cursor.x += (glyph.getAdvance().x); } stat.origin.x = transform.position.x * ::defaults.size.x; // ( !metadataGui.world && transform.position.x != (int) transform.position.x ) ? transform.position.x * ::defaults.size.x : transform.position.x; stat.origin.y = transform.position.y * ::defaults.size.y; // ( !metadataGui.world && transform.position.y != (int) transform.position.y ) ? transform.position.y * ::defaults.size.y : transform.position.y; if ( origin == "top" ) stat.origin.y = ::defaults.size.y - stat.origin.y - stat.biggest.y;// else stat.origin.y = stat.origin.y; if ( align == "right" ) stat.origin.x = ::defaults.size.x - stat.origin.x - stat.box.w;// else stat.origin.x = stat.origin.x; else if ( align == "center" ) stat.origin.x -= stat.box.w * 0.5f; } // Render Glyphs stat.cursor.x = 0; stat.cursor.y = stat.biggest.y; for ( auto it = str.begin(); it != str.end(); ++it ) { uint64_t c = *it; if ( c == '\n' ) { if ( direction == "down" ) stat.cursor.y += stat.biggest.y; else stat.cursor.y -= stat.biggest.y; stat.cursor.x = 0; continue; } else if ( c == '\t' ) { // Fixed movement vs Real Tabbing if ( false ) { stat.cursor.x += stat.average.tab; } else { stat.cursor.x = ((stat.cursor.x / stat.average.tab) + 1) * stat.average.tab; } continue; } else if ( c == ' ' ) { stat.cursor.x += stat.average.tab / 4.0f; continue; } else if ( c == COLOR_CTRL ) { ++stat.colors.index; continue; } auto key = hashGlyphSettings( c, metadata ); auto& glyph = glyphs.cache[font][key]; ::GlyphBox g; g.code = c; g.box.w = glyph.getSize().x; g.box.h = glyph.getSize().y; g.box.x = stat.cursor.x + (glyph.getBearing().x); g.box.y = stat.cursor.y - glyph.getBearing().y; // - (glyph.getSize().y - glyph.getBearing().y); stat.cursor.x += (glyph.getAdvance().x); if ( stat.colors.index < stat.colors.container.size() ) { g.color = stat.colors.container.at(stat.colors.index); } else { g.color = metadataGui.color; } g.box.x /= ::defaults.size.x; g.box.w /= ::defaults.size.x; g.box.y /= ::defaults.size.y; g.box.h /= ::defaults.size.y; gs.push_back(g); } return gs; } } void ext::GuiGlyphBehavior::initialize( uf::Object& self ) { auto& metadata = this->getComponent(); auto& metadataGui = this->getComponent(); auto& metadataJson = this->getComponent(); /* if ( ext::json::isNull( ::defaults.settings["metadata"] ) ) ::defaults.settings.readFromFile(uf::io::root+"/entities/gui/text/string.json"); // set defaults if ( metadataJson["string"].is() ) { auto copyMetadataJson = metadataJson; ext::json::forEach(::defaults.settings["metadata"], [&]( const uf::stl::string& key, ext::json::Value& value ){ if ( ext::json::isNull( copyMetadataJson[key] ) ) { metadataJson[key] = value; } }); } */ // if ( metadataJson["scaling"] == "auto" ) metadataJson["scaling"] = "none"; this->addHook( "gui:UpdateText.%UID%", [&](ext::json::Value& payload){ auto string = payload["string"].as(metadata.string); auto font = uf::io::root+"/fonts/" + payload["font"].as(metadata.font); bool forced = payload["force"].as(false); auto glyphs = ::generateGlyphs( self, string ); auto& scene = uf::scene::getCurrentScene(); auto& mesh = this->getComponent(); auto& atlas = this->getComponent(); auto& images = atlas.getImages(); auto& glyphs_cache = ::glyphs.cache; uf::stl::unordered_map glyph_atlas_map; metadataGui.scaleMode = "none"; // generate texture { atlas.clear(); for ( auto& g : glyphs ) { auto key = ::hashGlyphSettings( g.code, metadata ); auto& glyph = glyphs_cache[font][key]; const uint8_t* buffer = glyph.getBuffer(); uf::Image::container_t pixels; size_t len = glyph.getSize().x * glyph.getSize().y; if ( metadata.sdf ) { glyph_atlas_map[key] = atlas.addImage( glyph.getBuffer(), glyph.getSize(), 8, 1, true ); } else { #if 1 || UF_ENV_DREAMCAST pixels.reserve( len * 4 ); for ( size_t i = 0; i < len; ++i ) { pixels.emplace_back( buffer[i] ); pixels.emplace_back( buffer[i] ); pixels.emplace_back( buffer[i] ); pixels.emplace_back( buffer[i] ); // pixels.emplace_back( buffer[i] > 127 ? 255 : 0 ); } glyph_atlas_map[key] = atlas.addImage( &pixels[0], glyph.getSize(), 8, 4, true ); #else pixels.reserve( len * 2 ); for ( size_t i = 0; i < len; ++i ) { pixels.emplace_back( buffer[i] ); pixels.emplace_back( buffer[i] ); // pixels.emplace_back( buffer[i] > 127 ? 255 : 0 ); } glyph_atlas_map[key] = atlas.addImage( &pixels[0], glyph.getSize(), 8, 2, true ); #endif } } atlas.generate(); atlas.clear(false); } // generate mesh { mesh.destroy(); mesh.bind<::Mesh, uint16_t>(); uf::stl::vector<::Mesh> vertices; vertices.reserve( glyphs.size() * 6 ); uf::stl::vector indices; indices.reserve( glyphs.size() * 6 ); // pod::Vector2f min = { 0, 0 }; // pod::Vector2f max = { 0, 0 }; for ( auto& g : glyphs ) { auto key = ::hashGlyphSettings( g.code, metadata ); auto& glyph = glyphs_cache[font][key]; auto hash = glyph_atlas_map[key]; #if EXT_COLOR_FLOATS auto& color = g.color; #else pod::ColorRgba color = { g.color[0] * 255, g.color[1] * 255, g.color[2] * 255, g.color[3] * 255 }; #endif // add vertices vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x, g.box.y + g.box.h, 0 }, atlas.mapUv( pod::Vector2f{ 0.0f, 0.0f }, hash ), color}); indices.emplace_back( indices.size() ); vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x, g.box.y , 0 }, atlas.mapUv( pod::Vector2f{ 0.0f, 1.0f }, hash ), color}); indices.emplace_back( indices.size() ); vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x + g.box.w, g.box.y , 0 }, atlas.mapUv( pod::Vector2f{ 1.0f, 1.0f }, hash ), color}); indices.emplace_back( indices.size() ); vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x, g.box.y + g.box.h, 0 }, atlas.mapUv( pod::Vector2f{ 0.0f, 0.0f }, hash ), color}); indices.emplace_back( indices.size() ); vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x + g.box.w, g.box.y , 0 }, atlas.mapUv( pod::Vector2f{ 1.0f, 1.0f }, hash ), color}); indices.emplace_back( indices.size() ); vertices.emplace_back( ::Mesh{pod::Vector3f{ g.box.x + g.box.w, g.box.y + g.box.h, 0 }, atlas.mapUv( pod::Vector2f{ 1.0f, 0.0f }, hash ), color}); indices.emplace_back( indices.size() ); } /* pod::Vector2f min = { std::numeric_limits::max(), std::numeric_limits::max() }; pod::Vector2f max = { -std::numeric_limits::max(), -std::numeric_limits::max() }; for ( auto& vertex : vertices ) { min.x = std::min( min.x, vertex.position.x * ::defaults.size.x * transform.scale.x ); min.y = std::min( min.y, vertex.position.y * ::defaults.size.y * transform.scale.y ); max.x = std::max( max.x, vertex.position.x * ::defaults.size.x * transform.scale.x ); max.y = std::max( max.y, vertex.position.y * ::defaults.size.y * transform.scale.y ); #if UF_USE_OPENGL vertex.position.y = -vertex.position.y; #endif } metadataGui.size = { max.x - min.x, max.y - min.y, }; */ mesh.insertVertices( vertices ); mesh.insertIndices( indices ); // mesh.resizeVertices( metadata.reserve * 6 ); // mesh.resizeIndices( metadata.reserve * 6 ); } if ( metadata.sdf ) { metadataJson["shaders"]["vertex"] = uf::io::root+"/shaders/gui/text/vert.spv"; metadataJson["shaders"]["fragment"] = uf::io::root+"/shaders/gui/text/frag.spv"; } // fire image update { ext::payloads::GuiInitializationPayload payload; payload.image = &atlas.getAtlas(); payload.mesh = &mesh; payload.free = false; this->callHook( "gui:Update.%UID%", payload ); } }); UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson); } void ext::GuiGlyphBehavior::tick( uf::Object& self ) { if ( !this->hasComponent() ) return; #if !UF_USE_OPENGL auto& transform = this->getComponent>(); auto& metadata = this->getComponent(); auto& metadataJson = this->getComponent(); auto& mesh = this->getComponent(); auto& graphic = this->getComponent(); auto model = uf::matrix::identity(); auto& scene = uf::scene::getCurrentScene(); auto& controller = scene.getController(); auto& camera = controller.getComponent(); // bind UBO if ( graphic.material.hasShader("vertex") ) { auto& shader = graphic.material.getShader("vertex"); if ( shader.hasUniform("UBO_Glyph") ) { auto& uniformBuffer = shader.getUniformBuffer("UBO_Glyph"); struct Glyph { /*alignas(16)*/ pod::Vector4f stroke; /*alignas(8)*/ pod::Vector2i range; /*alignas(4)*/ int32_t spread; /*alignas(4)*/ float weight; /*alignas(4)*/ float scale; /*alignas(4)*/ float padding1; /*alignas(4)*/ float padding2; /*alignas(4)*/ float padding3; } ubo = { .stroke = metadata.shader.stroke, .range = metadata.shader.range, .spread = metadata.spread, .weight = metadata.shader.weight, .scale = metadata.shader.scale, }; // if ( metadataGlyph.sdf ) uniforms.gui.mode &= 1 << 1; // if ( metadataGlyph.shadowbox ) uniforms.gui.mode &= 1 << 2; shader.updateBuffer( (const void*) &ubo, sizeof(ubo), uniformBuffer ); } } #endif } void ext::GuiGlyphBehavior::render( uf::Object& self ){} void ext::GuiGlyphBehavior::destroy( uf::Object& self ){} #undef this void ext::GuiGlyphBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){ serializer["string"] = /*this->*/string; serializer["font"] = /*this->*/font; serializer["sdf"] = /*this->*/sdf; serializer["spread"] = /*this->*/spread; serializer["padding"] = uf::vector::encode( /*this->*/padding); serializer["scale"] = /*this->*/shader.scale; serializer["weight"] = /*this->*/shader.weight; serializer["stroke"] = uf::vector::encode( /*this->*/shader.stroke); serializer["range"] = uf::vector::encode( /*this->*/shader.range); } void ext::GuiGlyphBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){ size_t oldHash = ::hashGlyphSettings( string, *this ); /*this->*/string = serializer["string"].as(/*this->*/string); /*this->*/font = serializer["font"].as(/*this->*/font); /*this->*/sdf = serializer["sdf"].as(/*this->*/sdf); /*this->*/spread = serializer["spread"].as(/*this->*/spread); /*this->*/padding = uf::vector::decode(serializer["padding"], /*this->*/padding); /*this->*/shader.scale = serializer["scale"].as(/*this->*/shader.scale); /*this->*/shader.weight = serializer["weight"].as(/*this->*/shader.weight); /*this->*/shader.stroke = uf::vector::decode(serializer["stroke"], /*this->*/shader.stroke); /*this->*/shader.range = uf::vector::decode(serializer["range"], /*this->*/shader.range); size_t newHash = ::hashGlyphSettings( string, *this ); // fire text update if ( oldHash != newHash ) { ext::json::Value payload; payload["string"] = /*this->*/string; self.callHook("gui:UpdateText.%UID%", payload); } }