cleaned up GUI system ((re)added simple anchoring system, although I need to actually utilize it), fixed SDFs looking like shit

This commit is contained in:
ecker 2026-04-28 22:56:10 -05:00
parent 84f2b63a8f
commit 3965d3f719
23 changed files with 1316 additions and 776 deletions

View File

@ -60,7 +60,7 @@
},
"graph": {
"initial buffer elements": 128,
"global storage": true
"global storage": false
},
"ext": {
"vulkan": {
@ -109,7 +109,7 @@
"invariant": {
"default stage buffers": true,
"default defer buffer destroy": true,
"default command buffer immediate": true,
"default command buffer immediate": false,
"multithreaded recording": true
},
"pipelines": {
@ -117,10 +117,10 @@
"gui": true,
"vsync": true, // vsync on vulkan side rather than engine-side
"hdr": true,
"vxgi": false,
"vxgi": true,
"culling": true,
"bloom": false,
"dof": false,
"bloom": true,
"dof": true,
"rt": false,
"fsr": false,
"postProcess": false // "postProcess.chromab" // false
@ -363,7 +363,7 @@
"render modes": { "gui": true, "deferred": true },
"limiters": {
"deltaTime": 5,
"framerate": "auto" // "auto" // for some reason drops to 60
"framerate": 300 // "auto" // for some reason drops to 60
},
"threads": {
"workers" : "auto",

View File

@ -29,7 +29,7 @@
"physics": {
"gravity": [ 0, -9.81, 0 ],
"inertia": null,
"mass": 10,
"type": "bounding box",
"recenter": false
@ -51,7 +51,7 @@
},
"tags": {
"/^.*/": {
"optimize meshlets": { "simplify": 0.125, "print": true }
"optimize meshlets": { "simplify": 0.5, "lods": true, "print": true }
}
}
}

View File

@ -15,6 +15,6 @@
},
"metadata": {
"clickable": false,
"hoverable": false,
"hoverable": false
}
}

View File

@ -3,7 +3,7 @@
"type": "Gui",
"ignore": false,
"assets": [
{ "filename": "./textures/mp.png", "hash": "68e7c459f9aecd6815ff7df1e2eefa82db60a23713b0134f0bfc15d82f55453d" }
// { "filename": "./textures/mp.png", "hash": "68e7c459f9aecd6815ff7df1e2eefa82db60a23713b0134f0bfc15d82f55453d" }
// { "filename": "./textures/ss2.png" }
],
"transform": {
@ -20,16 +20,9 @@
"hoverable": false,
"uv": [ 0, 0, 1, 1 ],
// "color": [ 0.8, 0.8, 1, 1 ],
// "color": [ 0.416, 0.573, 0.667, 1 ],
"color": [ 1, 1, 1, 1 ],
"location": "",
"scaling": "relative",
// "depth": 0.1,
// "alpha": 0.5,
"alpha": 0.75,
"mode": 1,
"gui layer": true,
"only model": true
"mode": 1
}
}

View File

@ -10,6 +10,7 @@ for i=1, visorLayers do
end
local soundEmitter = ent:loadChild("./sound.json",true)
local text = ent:loadChild("./text.json",true)
local timer = Timer.new()
if not timer:running() then timer:start() end
@ -51,9 +52,9 @@ local rotate = function( delta )
end
end
local windowSize = masterdata["system"]["config"]["window"]["size"];
--local windowSize = masterdata["system"]["config"]["window"]["size"];
local entTransform = ent:getComponent("Transform")
entTransform.scale.x = entTransform.scale.x * windowSize.x / windowSize.y;
--entTransform.scale.x = entTransform.scale.x * windowSize.x / windowSize.y;
for k, obj in pairs(children) do
local transform = obj:getComponent("Transform")
@ -105,7 +106,17 @@ ent:addHook( "window:Mouse.Moved", function( payload )
end )
]]
local fpsCounter = {
frames = 0,
time = 0,
freq = 0.1
}
text:callHook( "gui:UpdateText.%UID%", {
string = ""
} )
ent:bind( "tick", function(self)
--[[
for k, obj in pairs(children) do
local metadata = obj:getComponent("Metadata")
local glow = math.sin(time.current()) * 0.5 + 0.5 -- constrained to [0,1]
@ -113,16 +124,33 @@ ent:bind( "tick", function(self)
metadata["alpha"] = glow
obj:setComponent("Metadata", metadata)
end
]]
if fpsCounter["time"] > fpsCounter["freq"] then
-- update text
text:callHook( "gui:UpdateText.%UID%", {
string = tostring(math.floor(fpsCounter["frames"] / fpsCounter["time"]))
} )
fpsCounter["time"] = 0
fpsCounter["frames"] = 0
else
fpsCounter["frames"] = fpsCounter["frames"] + 1
fpsCounter["time"] = fpsCounter["time"] + time.delta()
end
local controllerTransform = controller:getComponent("Transform")
local controllerCamera = controller:getComponent("Camera")
local controllerCameraTransform = controllerCamera:getTransform()
local transform = ent:getComponent("Transform")
--[[
local speed = 2.5
if lerper.a == 1 then return end
lerper.a = lerper.a + time.delta() * speed
if lerper.a > 1 then lerper.a = 1 end
]]
transform.orientation = lerper.from:slerp( lerper.to, lerper.a )
local orientation = transform.orientation

View File

@ -0,0 +1,25 @@
{
"name": "HUD Overlay",
"type": "Gui",
"ignore": false,
"assets": [
],
"transform": {
"position": [ -0.9, -0.9, 0 ],
"rotation": {
"axis": [ 0, 0, 1 ],
"angle": 0
},
"scale": [ 1, 1, 1 ]
},
"metadata": {
"clickable": false,
"hoverable": false,
"uv": [ 0, 0, 1, 1 ],
"color": [ 1, 1, 1, 1 ],
"scaling": "relative",
"string": "1234567890"
}
}

View File

@ -1,42 +0,0 @@
{
"name": "Gui: Text",
"type": "Gui",
"ignore": true,
"transform": {
"position": [ 0, 0, 0 ],
"rotation": {
"axis": [ 0, 0, 1 ],
"angle": 0
},
"scale": [ 1, 1, 1 ]
},
"metadata": {
"legacy": false,
"padding": [ 1, 1 ],
"spread": 4,
"weight": 0.48,
// "size": 36, "scale": 3,
// "size": 45, "scale": 2,
"size": 60, "scale": 1.5,
// "size": 72, "scale": 1.25,
// "size": 90, "scale": 1,
"sdf": false,
"font": "TAZUGANEGOTHICSTDN-BOLD.otf",
"kerning": 24,
"scaling": "none",
// "font": "Coolvetica.ttf",
"stroke": [ 0, 0, 0, 0 ],
"color": [ 1, 1, 1, 1 ],
"direction": "down",
"align": "left",
"origin": [ 0, 0 ],
"string": "",
"world": false,
// "depth": 0,
"wrap": true
}
}

View File

@ -17,13 +17,6 @@
},
"metadata": {
"uv": [ 0, 0, 1, 1 ],
"location": "",
"scaling": "relative",
"text settings": {
"stroke": [ 1, 0.749, 0.368, 1 ],
"color": [ 1, 0.749, 0.368, 1 ],
"depth": 0.5,
"string": "."
}
"scaling": "relative"
}
}

View File

@ -21,6 +21,7 @@
// "./playerModel.json",
"./playerLight.json",
// "./playerHands.json",
"./gui/hud/hud.json",
"./scripts/player.lua"
],
"system": {

View File

@ -6,7 +6,7 @@
"metadata": {
"holdable": true,
"physics": {
"mass": 100,
"mass": 100000,
"inertia": false,
"type": "bounding box"
// "type": "mesh"

View File

@ -2,8 +2,8 @@
"import": "./base_sourceengine.json",
"assets": [
// { "filename": "./models/mds_mcdonalds.glb" }
{ "filename": "./models/mds_mcdonalds/graph.json" }
// ,{ "filename": "/burger.json", "delay": 1 }
{ "filename": "./models/mds_mcdonalds/graph.json" },
{ "filename": "/burger.json", "delay": 1 }
],
"metadata": {
"graph": {

View File

@ -11,7 +11,7 @@ layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
#define QUERY_MIPMAPS 1
#define DEPTH_BIAS 0.00005
#define FRUSTUM_CULLING 1
#define OCCLUSION_CULLING 1 // currently whack
#define OCCLUSION_CULLING 0 // currently whack
#define LODS 1
#define MAX_LODS 4

View File

@ -30,7 +30,7 @@ void main() {
const float sampled = texture(samplerTexture, inUv).r;
const float smoothing = ( inGlyph.spread > 0 && inGlyph.scale > 0 ) ? 0.25 / (inGlyph.spread * inGlyph.scale) : 0.25 / (4 * 1.5);
const float outlining = smoothstep(0.5 - smoothing, 0.5 + smoothing, sampled);
const float outlining = smoothstep(inGlyph.fillWeight - smoothing, inGlyph.fillWeight + smoothing, sampled);
const float alpha = smoothstep(inGlyph.weight - smoothing, inGlyph.weight + smoothing, sampled);
if ( alpha < 0.001 || alpha > 1 ) discard;
C = mix(inGlyph.stroke, inGui.color, outlining);

View File

@ -15,8 +15,8 @@ struct Glyph {
int spread;
float weight;
float fillWeight;
float scale;
uint padding1;
uint padding2;
uint padding3;
};

File diff suppressed because it is too large Load Diff

View File

@ -42,7 +42,7 @@ namespace ext {
float targetError = 1e-2f / simplify;
float realError = 0.0f;
size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), indicesCount * simplify, targetError);
size_t realIndices = meshopt_simplify(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), indicesCount * simplify, targetError, 0, &realError);
// size_t realIndices = meshopt_simplifySloppy(&indicesSimplified[0], &indices[0], indicesCount, &meshlet.vertices[0].position.x, verticesCount, sizeof(T), targetIndices);
if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError);

View File

@ -28,14 +28,6 @@
#define EXT_COLOR_FLOATS 1
namespace {
struct {
bool initialized = false;
uf::Mesh mesh;
uf::Image image;
uf::Serializer settings;
} defaults;
struct Mesh {
pod::Vector3f position;
pod::Vector2f uv;
@ -77,20 +69,33 @@ UF_VERTEX_DESCRIPTOR(Mesh,
)
namespace {
uf::Image& generateImage( uf::Image& image ) {
image.open(uf::io::root+"/textures/missing.png");
return image;
// default to center
pod::Vector2f parseAnchor( const uf::stl::string& anchor, const pod::Vector2f& def = {0.5f, 0.5f} ) {
if ( anchor == "top-center" || anchor == "top" ) return {0.5f, 0.0f};
if ( anchor == "top-left" ) return {0.0f, 0.0f};
if ( anchor == "top-right" ) return {1.0f, 0.0f};
if ( anchor == "center" ) return {0.5f, 0.5f};
if ( anchor == "center-left" || anchor == "left" ) return {0.0f, 0.5f};
if ( anchor == "center-right" || anchor == "right" ) return {1.0f, 0.5f};
if ( anchor == "bottom-center" || anchor == "bottom" ) return {0.5f, 1.0f};
if ( anchor == "bottom-left" ) return {0.0f, 1.0f};
if ( anchor == "bottom-right" ) return {1.0f, 1.0f};
return def;
}
uf::Mesh& generateMesh( uf::Mesh& mesh, const pod::Vector4f& color = {1, 1, 1, 1} ) {
uf::Mesh& generateMesh( uf::Mesh& mesh, const pod::Vector4f& color, const pod::Vector2f& center ) {
mesh.bind<::Mesh, uint16_t>();
mesh.insertVertices<::Mesh>({
{ pod::Vector3f{-1.0f, 1.0f, 0.0f}, pod::Vector2f{0.0f, 0.0f}, color },
{ pod::Vector3f{-1.0f, -1.0f, 0.0f}, pod::Vector2f{0.0f, 1.0f}, color },
{ pod::Vector3f{ 1.0f, -1.0f, 0.0f}, pod::Vector2f{1.0f, 1.0f}, color },
{ pod::Vector3f{-1.0f, 1.0f, 0.0f} - center, pod::Vector2f{0.0f, 0.0f}, color },
{ pod::Vector3f{-1.0f, -1.0f, 0.0f} - center, pod::Vector2f{0.0f, 1.0f}, color },
{ pod::Vector3f{ 1.0f, -1.0f, 0.0f} - center, pod::Vector2f{1.0f, 1.0f}, color },
{ pod::Vector3f{ 1.0f, -1.0f, 0.0f}, pod::Vector2f{1.0f, 1.0f}, color },
{ pod::Vector3f{ 1.0f, 1.0f, 0.0f}, pod::Vector2f{1.0f, 0.0f}, color },
{ pod::Vector3f{-1.0f, 1.0f, 0.0f}, pod::Vector2f{0.0f, 0.0f}, color },
{ pod::Vector3f{ 1.0f, -1.0f, 0.0f} - center, pod::Vector2f{1.0f, 1.0f}, color },
{ pod::Vector3f{ 1.0f, 1.0f, 0.0f} - center, pod::Vector2f{1.0f, 0.0f}, color },
{ pod::Vector3f{-1.0f, 1.0f, 0.0f} - center, pod::Vector2f{0.0f, 0.0f}, color },
});
mesh.insertIndices<uint16_t>({
0, 1, 2, 3, 4, 5
@ -98,6 +103,10 @@ namespace {
return mesh;
}
uf::Mesh& generateMesh( uf::Mesh& mesh, const pod::Vector4f& color = {1, 1, 1, 1}, const uf::stl::string& alignment = "center" ) {
return ::generateMesh( mesh, color, ::parseAnchor( alignment ) * 2.0f - 1.0f );
}
}
// YUCK
@ -120,54 +129,11 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
auto& scene = uf::scene::getCurrentScene();
if ( !::defaults.initialized ) {
::defaults.initialized = true;
::generateImage( ::defaults.image );
::generateMesh( ::defaults.mesh );
}
/*
if ( ext::json::isNull( ::defaults.settings["metadata"] ) ) ::defaults.settings.readFromFile(uf::io::root+"/entities/gui.json");
// set defaults
if ( metadataJson["string"].is<uf::stl::string>() ) {
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;
}
});
}
*/
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);
this->addHook( "gui:Update.%UID%", [&](ext::payloads::GuiInitializationPayload& payload){
auto& graphic = this->getComponent<uf::Graphic>();
/*
if ( metadataJson["mode"].as<uf::stl::string>() == "flat" ) {
if ( ext::json::isNull(metadataJson["projection"]) ) metadataJson["projection"] = false;
if ( ext::json::isNull(metadataJson["flip uv"]) ) metadataJson["flip uv"] = true;
if ( ext::json::isNull(metadataJson["front face"]) ) metadataJson["front face"] = "ccw";
} else {
if ( ext::json::isNull(metadataJson["projection"]) ) metadataJson["projection"] = true;
if ( ext::json::isNull(metadataJson["flip uv"]) ) metadataJson["flip uv"] = false;
if ( ext::json::isNull(metadataJson["front face"]) ) metadataJson["front face"] = "cw";
}
if ( metadataJson["world"].as<bool>() ) {
// metadataJson["gui layer"] = false;
} else {
#if UF_USE_OPENGL
if ( ext::json::isNull(metadataJson["cull mode"]) ) metadataJson["cull mode"] = "front";
if ( uf::matrix::reverseInfiniteProjection ) metadata.depth = 1 - metadata.depth;
// transform.position.z = metadata.depth;
#else
// if ( metadataJson["flip uv"].as<bool>() ) for ( auto& v : vertices ) v.uv.y = 1 - v.uv.y;
#endif
// for ( auto& v : vertices ) v.position.z = metadata.depth;
}
*/
if ( ext::json::isNull(metadataJson["cull mode"]) ) metadataJson["cull mode"] = "back";
//
@ -186,11 +152,6 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
auto& texture = graphic.material.textures.emplace_back();
texture.loadFromImage( image );
// update transform
{
}
if ( payload.free ) {
delete payload.image;
@ -263,6 +224,16 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
}
}
if ( metadata.space == "screen" ) {
auto anchor = ::parseAnchor( metadata.anchor, metadata.pivot ) * 2.0f - 1.0f;
transform.position.x += anchor.x;
transform.position.y += anchor.y;
/*
transform.position.x += anchor.x * ((float) metadata.size.x / uf::renderer::settings::width) * transform.scale.x;
transform.position.y += anchor.y * ((float) metadata.size.y / uf::renderer::settings::height) * transform.scale.y;
*/
}
metadata.initialized = true;
});
@ -274,7 +245,7 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
auto& image = uf::asset::get<uf::Image>( payload );
// generate default mesh
::generateMesh( mesh, metadata.color );
::generateMesh( mesh, metadata.color, metadata.alignment );
{
ext::payloads::GuiInitializationPayload payload;
@ -291,12 +262,9 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
if ( !clickTimer.running() ) clickTimer.start();
this->addHook( "window:Mouse.Click", [&](pod::payloads::windowMouseClick& payload){
if ( metadata.world ) return;
//if ( !metadata.boxMin && !metadata.boxMax ) return;
if ( metadata.space != "screen" ) return;
if ((metadata.boxMin.x > metadata.boxMax.x)||(metadata.boxMin.y > metadata.boxMax.y)) return;
// uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager");
// pod::Vector2ui guiSize = manager ? manager->getComponent<ext::GuiManagerBehavior::Metadata>().size : pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height };
pod::Vector2ui guiSize = pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height };
bool clicked = false;
@ -386,12 +354,9 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
hoverTimer.start( uf::Time<>(-1000000) );
this->addHook( "window:Mouse.Moved", [&](pod::payloads::windowMouseMoved& payload){
if ( metadata.world ) return;
//if ( !metadata.boxMin && !metadata.boxMax ) return;
if ( metadata.space != "screen" ) return;
if ((metadata.boxMin.x > metadata.boxMax.x)||(metadata.boxMin.y > metadata.boxMax.y)) return;
// uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager");
// pod::Vector2ui guiSize = manager ? manager->getComponent<ext::GuiManagerBehavior::Metadata>().size : pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height };
pod::Vector2ui guiSize = pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height };
bool hovered = false;
@ -488,22 +453,18 @@ void ext::GuiBehavior::tick( uf::Object& self ) {
auto& controller = scene.getController();
auto& camera = controller.getComponent<uf::Camera>();
// uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager");
// pod::Vector2ui guiSize = manager ? manager->getComponent<ext::GuiManagerBehavior::Metadata>().size : pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height };
pod::Vector2ui guiSize = pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height };
if ( metadata.space == "screen" ) {
pod::Vector2f guiSize = pod::Vector2f{ uf::renderer::settings::width, uf::renderer::settings::height };
pod::Vector2f scale = { 1, 1 };
if ( metadata.scaleMode == "fixed" || metadata.scaleMode == "fixed-x" ) {
flatten.scale.x *= (float) metadata.size.x / (float) guiSize.x;
}
if ( metadata.scaleMode == "fixed" || metadata.scaleMode == "fixed-y" ) {
flatten.scale.y *= (float) metadata.size.x / (float) guiSize.y;
}
if ( metadata.scaling == "fixed" || metadata.scaling == "fixed-x" ) scale.x = (float) metadata.size.x / guiSize.x;
if ( metadata.scaling == "fixed" || metadata.scaling == "fixed-y" ) scale.y = (float) metadata.size.x / guiSize.y;
if ( metadata.scaleMode == "relative" || metadata.scaleMode == "relative-x" ) {
flatten.scale.x *= (float) guiSize.y / (float) guiSize.x;
}
if ( metadata.scaleMode == "relative" || metadata.scaleMode == "relative-y" ) {
flatten.scale.y *= (float) guiSize.x / (float) guiSize.y;
if ( metadata.scaling == "relative" || metadata.scaling == "relative-x" ) scale.x = (float) guiSize.y / guiSize.x;
if ( metadata.scaling == "relative" || metadata.scaling == "relative-y" ) scale.y = (float) guiSize.x / guiSize.y;
flatten.scale.x *= scale.x;
flatten.scale.y *= scale.y;
}
// bind UBO
@ -619,33 +580,48 @@ void ext::GuiBehavior::tick( uf::Object& self ) {
void ext::GuiBehavior::render( uf::Object& self ){}
void ext::GuiBehavior::destroy( uf::Object& self ){}
void ext::GuiBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){
serializer["color"] = uf::vector::encode( /*this->*/color );
serializer["uv"] = uf::vector::encode( /*this->*/uv );
// serializer["scaling"] = uf::vector::encode( /*this->*/scaling );
serializer["depth"] = /*this->*/depth;
serializer["mode"] = /*this->*/mode;
serializer["renderMode"] = /*this->*/renderMode;
serializer["scaling"] = /*this->*/scaleMode;
serializer["clickable"] = /*this->*/clickable;
serializer["clicked"] = /*this->*/clicked;
serializer["hoverable"] = /*this->*/hoverable;
serializer["hovered"] = /*this->*/hovered;
serializer["size"] = uf::vector::encode( /*this->*/size );
serializer["uv"] = uf::vector::encode( /*this->*/uv );
serializer["color"] = uf::vector::encode( /*this->*/color );
serializer["renderMode"] = /*this->*/renderMode;
serializer["scaling"] = /*this->*/scaling;
//
serializer["anchor"] = /*this->*/anchor;
serializer["alignment"] = /*this->*/alignment;
serializer["space"] = /*this->*/space;
serializer["pivot"] = uf::vector::encode(/*this->*/pivot);
}
void ext::GuiBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer& serializer ){
/*this->*/color = uf::vector::decode( serializer["color"], /*this->*/color );
/*this->*/uv = uf::vector::decode( serializer["uv"], /*this->*/uv );
// /*this->*/scaling = uf::vector::decode( serializer["scaling"], /*this->*/scaling );
/*this->*/depth = serializer["depth"].as( /*this->*/depth );
/*this->*/mode = serializer["mode"].as( /*this->*/mode );
/*this->*/renderMode = serializer["renderMode"].as( /*this->*/renderMode );
/*this->*/scaleMode = serializer["scaling"].as( /*this->*/scaleMode );
/*this->*/clickable = serializer["clickable"].as( /*this->*/clickable );
// /*this->*/clicked = serializer["clicked"].as( /*this->*/clicked );
/*this->*/hoverable = serializer["hoverable"].as( /*this->*/hoverable );
// /*this->*/hovered = serializer["hovered"].as( /*this->*/hovered );
/*this->*/size = uf::vector::decode( serializer["size"], /*this->*/size );
/*this->*/uv = uf::vector::decode( serializer["uv"], /*this->*/uv );
/*this->*/color = uf::vector::decode( serializer["color"], /*this->*/color );
/*this->*/renderMode = serializer["renderMode"].as( /*this->*/renderMode );
/*this->*/scaling = serializer["scaling"].as( /*this->*/scaling );
/*this->*/anchor = serializer["anchor"].as( /*this->*/anchor );
/*this->*/alignment = serializer["alignment"].as( /*this->*/alignment );
/*this->*/space = serializer["space"].as( /*this->*/space );
/*this->*/pivot = uf::vector::decode( serializer["pivot"], /*this->*/pivot );
}
#undef this
@ -653,7 +629,6 @@ void ext::GuiBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer&
#include <uf/ext/lua/component.h>
UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::GuiBehavior::Metadata,
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::initialized),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::world),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::depth),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::mode),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::clickable),
@ -661,11 +636,14 @@ UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::GuiBehavior::Metadata,
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::hoverable),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::hovered),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::size),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::scale),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::uv),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::color),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::renderMode),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::scaleMode),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::scaling),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::boxMin),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::boxMax)
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::boxMax),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::anchor),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::alignment),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::space),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::pivot)
)

View File

@ -20,8 +20,6 @@ namespace ext {
UF_BEHAVIOR_DEFINE_METADATA(
bool initialized = false;
bool world = false;
float depth = 0;
size_t mode = 0;
@ -31,15 +29,19 @@ namespace ext {
bool hovered = false;
pod::Vector2ui size = {};
pod::Vector2f scale = { 1, 1 };
pod::Vector4f uv = { 0, 0, 1, 1 };
pod::Vector4f color = { 1, 1, 1, 1 };
uf::stl::string renderMode = "Gui";
uf::stl::string scaleMode = "fixed";
uf::stl::string scaling = "fixed";
pod::Vector2f boxMin = { 1, 1 };
pod::Vector2f boxMax = { 1, 1 };
uf::stl::string space = "screen";
pod::Vector2f pivot = {0.5f, 0.5f}; // if anchor is not a valid string
uf::stl::string anchor = ""; // blank as the default changes between normal GUI and glyph
uf::stl::string alignment = ""; // ^
);
}
}

View File

@ -108,226 +108,268 @@ namespace {
return seed;
}
uf::stl::vector<::GlyphBox> generateGlyphs( uf::Object& self, const uf::stl::string& _string ) {
uf::stl::vector<::GlyphBox> gs;
#if UF_USE_FREETYPE
auto& glyphs = ::glyphs;
auto& metadata = this->getComponent<ext::GuiGlyphBehavior::Metadata>();
auto& metadataGui = this->getComponent<ext::GuiBehavior::Metadata>();
auto& transform = this->getComponent<pod::Transform<>>();
// default to left
pod::Vector2f parseAnchor( const uf::stl::string& anchor, const pod::Vector2f& def = {0.0f, 0.0f} ) {
if ( anchor == "top-center" || anchor == "top" ) return {0.5f, 0.0f};
if ( anchor == "top-left" ) return {0.0f, 0.0f};
if ( anchor == "top-right" ) return {1.0f, 0.0f};
if ( anchor == "center" ) return {0.5f, 0.5f};
if ( anchor == "center-left" || anchor == "left" ) return {0.0f, 0.5f};
if ( anchor == "center-right" || anchor == "right" ) return {1.0f, 0.5f};
auto string = _string == "" ? metadata.string : _string;
auto font = uf::io::root+"/fonts/" + metadata.font;
auto color = metadataGui.color;
if ( anchor == "bottom-center" || anchor == "bottom" ) return {0.5f, 1.0f};
if ( anchor == "bottom-left" ) return {0.0f, 1.0f};
if ( anchor == "bottom-right" ) return {1.0f, 1.0f};
auto origin = uf::stl::string("top");
auto align = uf::stl::string("center");
auto direction = uf::stl::string("down");
return def;
}
if ( glyphs.cache[font].empty() ) ext::freetype::initialize( glyphs.glyph, font );
struct TextToken {
uf::stl::string text;
pod::Vector3f color;
};
struct {
struct {
float x = 0;
float y = 0;
} origin;
struct {
float x = 0;
float y = 0;
} cursor;
float hexToFloat( const uf::stl::string& str ) {
int value;
uf::stl::stringstream stream;
stream << str;
stream >> std::hex >> value;
return value / 255.0f;
}
struct {
float sum = 0;
float len = 0;
float proc = 0;
float tab = 4;
} average;
struct {
float x = 0;
float y = 0;
} biggest;
// parses a text for special tokens, associating strings with color
// should maybe be utf8, but in theory it shouldn't matter since tags are ASCII
uf::stl::vector<TextToken> parseTextTokens( const uf::stl::string& text, pod::Vector3f color ) {
uf::stl::vector<TextToken> tokens;
struct {
float w = 0;
float h = 0;
} box;
auto tagLength = 10; // hard-coded cringe
bool colorChanged = false;
size_t currentPos = 0;
size_t textLength = text.length();
struct {
uf::stl::vector<pod::Vector3f> container;
size_t index = 0;
} colors;
} stat;
TextToken currentToken;
currentToken.color = color;
stat.colors.container.push_back(color);
while ( currentPos < textLength ) {
size_t tagStart = text.find("${#", currentPos);
// grab escaped color markers: ${#RRGGBB}
{
uf::stl::unordered_map<size_t, pod::Vector3f> 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 ( tagStart == uf::stl::string::npos ) {
currentToken.text += text.substr(currentPos);
if ( !currentToken.text.empty() || colorChanged ) {
tokens.emplace_back(currentToken);
}
break;
}
if ( tagStart > currentPos ) {
currentToken.text += text.substr( currentPos, tagStart - currentPos ); // append everything up to the tag
tokens.emplace_back( currentToken ); // commit token
currentToken.text = ""; // reset text
}
// validate tag
if ( tagStart + tagLength <= textLength && text[tagStart + tagLength - 1] == '}' ) {
uf::stl::string rHex = text.substr(tagStart + 3, 2);
uf::stl::string gHex = text.substr(tagStart + 5, 2);
uf::stl::string bHex = text.substr(tagStart + 7, 2);
// change color
currentToken.color = { hexToFloat(rHex), hexToFloat(gHex), hexToFloat(bHex) };
colorChanged = true;
// advance past the tag
currentPos = tagStart + tagLength;
} else {
currentToken.text += "${#"; // treat it as normal text
currentPos = tagStart + 3;
}
if ( maxTries == 0 ) text += "\n(error formatting)";
string = text;
}
#if 0
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> convert;
std::wstring str = convert.from_bytes(string);
#else
std::u8string str(string.begin(), string.end());
#endif
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];
return tokens;
}
// compute the boxes for a given string and settings
uf::stl::vector<GlyphBox> calculateGlyphLayout( uf::Object& self, const uf::stl::vector<TextToken>& tokens, const ext::GuiGlyphBehavior::Metadata& metadata, const ext::GuiBehavior::Metadata& metadataGui, const uf::stl::string& font ) {
uf::stl::vector<GlyphBox> layout;
auto& glyphsCache = ::glyphs.cache[font];
if ( glyphsCache.empty() ) {
ext::freetype::initialize( ::glyphs.glyph, font );
}
pod::Vector2f anchor = ::parseAnchor( metadataGui.alignment );
pod::Vector2f cursor = { 0.0f, 0.0f };
float maxTextWidth = 0.0f;
float maxTextHeight = 0.0f;
float tallestGlyphY = 0.0f;
float averageTabWidth = 0.0f;
float totalWidth = 0.0f;
int charCount = 0;
// generate glyph and line height
for ( const auto& token : tokens ) {
std::u8string str(token.text.begin(), token.text.end());
for ( uint64_t c : str ) {
if ( c == '\n' || c == '\t' ) continue; // special characters
auto key = hashGlyphSettings(c, metadata);
auto& glyph = glyphsCache[key];
// generate glyph
if ( !glyph.generated() ) {
glyph.setPadding( { metadata.padding[0], metadata.padding[1] } );
glyph.setSpread( metadata.spread );
glyph.useSdf( metadata.sdf );
glyph.generate( glyphs.glyph, c, metadata.size );
glyph.setPadding({ metadata.padding[0], metadata.padding[1] });
glyph.setSpread(metadata.spread);
glyph.useSdf(metadata.sdf);
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;
tallestGlyphY = std::max(tallestGlyphY, (float) glyph.getSize().y);
totalWidth += glyph.getSize().x; // should probably be reset on new-line to find the widest line
charCount++;
}
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;
if ( charCount > 0 ) averageTabWidth = (totalWidth / charCount) * 4.0f;
cursor.y = tallestGlyphY;
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;
// calculate positions
for ( const auto& token : tokens ) {
std::u8string str( token.text.begin(), token.text.end() );
for ( uint64_t c : str ) {
// advance cursor on special characters
if ( c == '\n' ) {
cursor.y += tallestGlyphY;
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;
}
cursor.x = ((int)(cursor.x / averageTabWidth) + 1) * averageTabWidth;
continue;
} else if ( c == COLOR_CTRL ) {
} else if ( c == ' ' ) {
cursor.x += averageTabWidth / 4.0f;
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);
// retrieve glyph
auto key = hashGlyphSettings(c, metadata);
auto& glyph = glyphsCache[key];
auto& g = layout.emplace_back(GlyphBox{
.box = {
.x = cursor.x + glyph.getBearing().x,
.y = cursor.y - glyph.getBearing().y,
.w = glyph.getSize().x,
.h = glyph.getSize().y,
},
.color = token.color,
.code = c,
});
stat.cursor.x += (glyph.getAdvance().x);
// advance cursor
cursor.x += glyph.getAdvance().x;
// advance bounding box
maxTextWidth = std::max(maxTextWidth, g.box.x + g.box.w);
maxTextHeight = std::max(maxTextHeight, g.box.y + g.box.h);
}
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];
// calculate offset based on anchor
float offsetX = maxTextWidth * anchor.x;
float offsetY = maxTextHeight * anchor.y;
::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;
}
// adjust all glyphs for our offset
for ( auto& g : layout ) {
g.box.x -= offsetX;
g.box.y -= offsetY;
// normalize
g.box.x /= ::defaults.size.x;
g.box.w /= ::defaults.size.x;
g.box.y /= ::defaults.size.y;
g.box.h /= ::defaults.size.y;
}
return layout;
}
gs.push_back(g);
// generate the mesh and texture atlas
void generateAtlasAndMesh( const uf::stl::vector<GlyphBox>& layout, const ext::GuiGlyphBehavior::Metadata& metadata, const uf::stl::string& font, uf::Atlas& atlas, uf::Mesh& mesh ) {
auto& glyphs_cache = ::glyphs.cache[font];
uf::stl::unordered_map<size_t, uf::stl::string> glyph_atlas_map;
#if UF_USE_FREETYPE
// generate atlas
{
atlas.clear();
// generate atlas
for ( const auto& g : layout ) {
auto key = hashGlyphSettings(g.code, metadata);
auto& glyph = glyphs_cache[key];
// already in atlas map
if ( glyph_atlas_map.find(key) != glyph_atlas_map.end() ) continue;
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 {
pixels.reserve(len * 4);
for ( size_t i = 0; i < len; ++i ) {
pixels.emplace_back(buffer[i]); // R
pixels.emplace_back(buffer[i]); // G
pixels.emplace_back(buffer[i]); // B
pixels.emplace_back(buffer[i]); // A
}
glyph_atlas_map[key] = atlas.addImage(&pixels[0], glyph.getSize(), 8, 4, true);
}
}
atlas.generate();
atlas.clear(false);
}
#endif
// generate mesh
{
mesh.destroy();
mesh.bind<::Mesh, uint16_t>();
return gs;
uf::stl::vector<::Mesh> vertices;
uf::stl::vector<uint16_t> indices;
vertices.reserve(layout.size() * 4);
indices.reserve(layout.size() * 6);
for ( const auto& g : layout ) {
auto key = hashGlyphSettings(g.code, metadata);
auto hash = glyph_atlas_map[key];
#if EXT_COLOR_FLOATS
auto& color = g.color;
#else
pod::ColorRgba color = {
(uint8_t)(g.color[0] * 255),
(uint8_t)(g.color[1] * 255),
(uint8_t)(g.color[2] * 255),
(uint8_t)(g.color[3] * 255)
};
#endif
// insert indices
uint16_t idx = (uint16_t) vertices.size();
indices.insert( indices.end(), { idx, idx + 1, idx + 2, idx, idx + 2, idx + 3 });
// insert 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});
vertices.emplace_back(::Mesh{pod::Vector3f{ g.box.x, g.box.y , 0 }, atlas.mapUv(pod::Vector2f{ 0.0f, 1.0f }, hash), color});
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});
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});
}
mesh.insertVertices(vertices);
mesh.insertIndices(indices);
}
}
}
@ -336,29 +378,14 @@ void ext::GuiGlyphBehavior::initialize( uf::Object& self ) {
auto& metadataGui = this->getComponent<ext::GuiBehavior::Metadata>();
auto& metadataJson = this->getComponent<uf::Serializer>();
/*
if ( ext::json::isNull( ::defaults.settings["metadata"] ) ) ::defaults.settings.readFromFile(uf::io::root+"/entities/gui/text/string.json");
// set defaults
if ( metadataJson["string"].is<uf::stl::string>() ) {
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);
metadata.sdf = false;
auto glyphs = ::generateGlyphs( self, string );
// override
// metadata.sdf = false;
metadataGui.scaling = "none";
auto& scene = uf::scene::getCurrentScene();
auto& mesh = this->getComponent<uf::Mesh>();
@ -368,110 +395,12 @@ void ext::GuiGlyphBehavior::initialize( uf::Object& self ) {
uf::stl::unordered_map<size_t, uf::stl::string> glyph_atlas_map;
metadataGui.scaleMode = "none";
// generate texture
#if UF_USE_FREETYPE
{
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 {
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 );
#if 0
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);
}
#endif
// generate mesh
{
mesh.destroy();
mesh.bind<::Mesh, uint16_t>();
uf::stl::vector<::Mesh> vertices; vertices.reserve( glyphs.size() * 6 );
uf::stl::vector<uint16_t> 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<float>::max(), std::numeric_limits<float>::max() };
pod::Vector2f max = { -std::numeric_limits<float>::max(), -std::numeric_limits<float>::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 );
}
auto tokens = ::parseTextTokens(string, metadataGui.color);
auto layout = ::calculateGlyphLayout(self, tokens, metadata, metadataGui, font);
::generateAtlasAndMesh( layout, metadata, font, atlas, mesh );
// set proper shaders
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";
@ -518,22 +447,19 @@ void ext::GuiGlyphBehavior::tick( uf::Object& self ) {
/*alignas(4)*/ int32_t spread;
/*alignas(4)*/ float weight;
/*alignas(4)*/ float fillWeight;
/*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,
.fillWeight = metadata.shader.fillWeight,
.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 );
}
@ -554,6 +480,7 @@ void ext::GuiGlyphBehavior::Metadata::serialize( uf::Object& self, uf::Serialize
serializer["scale"] = /*this->*/shader.scale;
serializer["weight"] = /*this->*/shader.weight;
serializer["fillWeight"] = /*this->*/shader.fillWeight;
serializer["stroke"] = uf::vector::encode( /*this->*/shader.stroke);
serializer["range"] = uf::vector::encode( /*this->*/shader.range);
}
@ -568,6 +495,7 @@ void ext::GuiGlyphBehavior::Metadata::deserialize( uf::Object& self, uf::Seriali
/*this->*/shader.scale = serializer["scale"].as(/*this->*/shader.scale);
/*this->*/shader.weight = serializer["weight"].as(/*this->*/shader.weight);
/*this->*/shader.fillWeight = serializer["fillWeight"].as(/*this->*/shader.fillWeight);
/*this->*/shader.stroke = uf::vector::decode(serializer["stroke"], /*this->*/shader.stroke);
/*this->*/shader.range = uf::vector::decode(serializer["range"], /*this->*/shader.range);

View File

@ -16,15 +16,16 @@ namespace ext {
bool sdf = true;
float size = 96;
float spread = 2;
float spread = 8;
pod::Vector2ui padding = { 2, 2 };
pod::Vector2ui padding = { 8, 8 };
size_t reserve = 128;
struct {
float scale = 1.5;
float weight = 0.45;
float fillWeight = 0.5;
pod::Vector4f stroke = { 0, 0, 0, 1 };
pod::Vector2i range = { -1, -1 };
} shader;

View File

@ -535,6 +535,7 @@ namespace {
}
void bindInstanceAddresses( uf::renderer::Graphic& graphic, uf::Mesh& mesh, uf::stl::vector<pod::Instance::Addresses>& addresses ) {
#if UF_USE_VULKAN
if ( !uf::renderer::settings::invariant::deviceAddressing || !mesh.indirect.count ) return;
addresses.resize( mesh.indirect.count );
@ -574,6 +575,7 @@ namespace {
instanceAddresses.drawID = drawID;
}
}
#endif
}
}
@ -1576,6 +1578,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) {
if ( !soft ) {
storage.buffers.camera.destroy(true);
storage.buffers.drawCommands.destroy(true);
storage.buffers.lodMetadata.destroy(true);
storage.buffers.instance.destroy(true);
storage.buffers.instanceAddresses.destroy(true);
storage.buffers.joint.destroy(true);
@ -1583,6 +1586,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) {
storage.buffers.material.destroy(true);
storage.buffers.texture.destroy(true);
storage.buffers.light.destroy(true);
storage.buffers.depthPyramid.destroy(true);
}
uf::renderer::states::rebuild = true;
@ -1777,6 +1781,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
drawCommands[drawID].vertexID = 0;
} else {
drawCommands[drawID] = primitives[drawID].drawCommand;
// to-do: LOD pick here for OpenGL
}
}

View File

@ -62,22 +62,8 @@ for ( auto& p : m.primitives ) {
if ( attribute.name == "POSITION" ) {
meshlet.vertices.resize(accessor.count);
if ( !graph.metadata["renderer"]["invert"].as<bool>(true) ){
meshlet.primitive.instance.bounds.min = pod::Vector3f{ accessor.minValues[0], accessor.minValues[1], accessor.minValues[2] };
meshlet.primitive.instance.bounds.max = pod::Vector3f{ accessor.maxValues[0], accessor.maxValues[1], accessor.maxValues[2] };
} else {
meshlet.primitive.instance.bounds.min = pod::Vector3f{ std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max() };
meshlet.primitive.instance.bounds.max = pod::Vector3f{ -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max() };
}
/*
meshlet.primitive.instance.bounds.min = pod::Vector3f{ accessor.minValues[0], accessor.minValues[1], accessor.minValues[2] };
meshlet.primitive.instance.bounds.max = pod::Vector3f{ accessor.maxValues[0], accessor.maxValues[1], accessor.maxValues[2] };
if ( graph.metadata["renderer"]["invert"].as<bool>(true) ){
meshlet.primitive.instance.bounds.min.x = -meshlet.primitive.instance.bounds.min.x;
meshlet.primitive.instance.bounds.max.x = -meshlet.primitive.instance.bounds.max.x;
}
*/
}
switch ( accessor.componentType ) {

View File

@ -1,6 +1,7 @@
#include <uf/ext/meshopt/meshopt.h>
#if UF_USE_MESHOPT
#include <meshoptimizer.h>
#include <cfloat>
bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verbose ) {
if ( mesh.isInterleaved() ) {
@ -53,7 +54,7 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verb
if ( 0.0f < simplify && simplify < 1.0f ) {
uf::stl::vector<uint32_t> indicesSimplified(indicesCount);
float targetError = 0.1; // 1e-2f / simplify;
float targetError = FLT_MAX; // 1e-2f / simplify;
float realError = 0.0f;
size_t realIndices = meshopt_simplify(
@ -64,8 +65,8 @@ bool ext::meshopt::optimize( uf::Mesh& mesh, float simplify, size_t o, bool verb
mesh.vertex.count,
positionsView.stride(),
indicesCount * simplify,
targetError
//,0, &realError
targetError,
0, &realError
);
if ( verbose ) {
@ -156,24 +157,26 @@ uf::stl::vector<pod::LODMetadata> ext::meshopt::generateLODs( uf::Mesh& mesh, co
meshopt_optimizeVertexCache(&baseIndices[0], &baseIndices[0], baseIndicesCount, mesh.vertex.count);
size_t previousIndicesCount = baseIndicesCount;
for ( size_t lodIdx = 0; lodIdx < numLODs; ++lodIdx ) {
float simplify = lodFactors[lodIdx];
uf::stl::vector<uint32_t> lodIndices = baseIndices;
size_t currentIndicesCount = baseIndicesCount;
if ( simplify < 1.0f ) {
float targetError = 0.1; // 1e-2f / simplify;
float targetError = FLT_MAX; // 1e-2f / simplify;
float realError = 0.0f;
currentIndicesCount = meshopt_simplify(
&lodIndices[0], &baseIndices[0], baseIndicesCount,
(const float*)positionsView.data(0), mesh.vertex.count, positionsView.stride(),
baseIndicesCount * simplify, targetError
//, 0, &realError
baseIndicesCount * simplify, targetError,
0, &realError
);
if ( baseIndicesCount == currentIndicesCount ) {
if ( previousIndicesCount == currentIndicesCount ) {
continue;
}
previousIndicesCount = currentIndicesCount;
if ( verbose ) {
UF_MSG_DEBUG("[View {} Simplified LOD {}] indices: {} -> {} | error: {} -> {}", viewIdx, lodIdx, baseIndicesCount, currentIndicesCount, targetError, realError);