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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,8 +2,8 @@
"import": "./base_sourceengine.json", "import": "./base_sourceengine.json",
"assets": [ "assets": [
// { "filename": "./models/mds_mcdonalds.glb" } // { "filename": "./models/mds_mcdonalds.glb" }
{ "filename": "./models/mds_mcdonalds/graph.json" } { "filename": "./models/mds_mcdonalds/graph.json" },
// ,{ "filename": "/burger.json", "delay": 1 } { "filename": "/burger.json", "delay": 1 }
], ],
"metadata": { "metadata": {
"graph": { "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 QUERY_MIPMAPS 1
#define DEPTH_BIAS 0.00005 #define DEPTH_BIAS 0.00005
#define FRUSTUM_CULLING 1 #define FRUSTUM_CULLING 1
#define OCCLUSION_CULLING 1 // currently whack #define OCCLUSION_CULLING 0 // currently whack
#define LODS 1 #define LODS 1
#define MAX_LODS 4 #define MAX_LODS 4

View File

@ -30,7 +30,7 @@ void main() {
const float sampled = texture(samplerTexture, inUv).r; 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 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); const float alpha = smoothstep(inGlyph.weight - smoothing, inGlyph.weight + smoothing, sampled);
if ( alpha < 0.001 || alpha > 1 ) discard; if ( alpha < 0.001 || alpha > 1 ) discard;
C = mix(inGlyph.stroke, inGui.color, outlining); C = mix(inGlyph.stroke, inGui.color, outlining);

View File

@ -15,8 +15,8 @@ struct Glyph {
int spread; int spread;
float weight; float weight;
float fillWeight;
float scale; float scale;
uint padding1;
uint padding2; uint padding2;
uint padding3; 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 targetError = 1e-2f / simplify;
float realError = 0.0f; 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); // 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); if ( verbose ) UF_MSG_DEBUG("[Simplified] indices: {} -> {} | error: {} -> {}", indicesCount, realIndices, targetError, realError);

View File

@ -28,14 +28,6 @@
#define EXT_COLOR_FLOATS 1 #define EXT_COLOR_FLOATS 1
namespace { namespace {
struct {
bool initialized = false;
uf::Mesh mesh;
uf::Image image;
uf::Serializer settings;
} defaults;
struct Mesh { struct Mesh {
pod::Vector3f position; pod::Vector3f position;
pod::Vector2f uv; pod::Vector2f uv;
@ -77,20 +69,33 @@ UF_VERTEX_DESCRIPTOR(Mesh,
) )
namespace { namespace {
uf::Image& generateImage( uf::Image& image ) { // default to center
image.open(uf::io::root+"/textures/missing.png"); pod::Vector2f parseAnchor( const uf::stl::string& anchor, const pod::Vector2f& def = {0.5f, 0.5f} ) {
return image; 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.bind<::Mesh, uint16_t>();
mesh.insertVertices<::Mesh>({ 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} - center, 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} - center, 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{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} - center, 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} - center, 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{0.0f, 0.0f}, color },
}); });
mesh.insertIndices<uint16_t>({ mesh.insertIndices<uint16_t>({
0, 1, 2, 3, 4, 5 0, 1, 2, 3, 4, 5
@ -98,6 +103,10 @@ namespace {
return mesh; 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 // YUCK
@ -120,54 +129,11 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
auto& scene = uf::scene::getCurrentScene(); 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); UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);
this->addHook( "gui:Update.%UID%", [&](ext::payloads::GuiInitializationPayload& payload){ this->addHook( "gui:Update.%UID%", [&](ext::payloads::GuiInitializationPayload& payload){
auto& graphic = this->getComponent<uf::Graphic>(); 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"; 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(); auto& texture = graphic.material.textures.emplace_back();
texture.loadFromImage( image ); texture.loadFromImage( image );
// update transform
{
}
if ( payload.free ) { if ( payload.free ) {
delete payload.image; 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; metadata.initialized = true;
}); });
@ -274,7 +245,7 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
auto& image = uf::asset::get<uf::Image>( payload ); auto& image = uf::asset::get<uf::Image>( payload );
// generate default mesh // generate default mesh
::generateMesh( mesh, metadata.color ); ::generateMesh( mesh, metadata.color, metadata.alignment );
{ {
ext::payloads::GuiInitializationPayload payload; ext::payloads::GuiInitializationPayload payload;
@ -291,12 +262,9 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
if ( !clickTimer.running() ) clickTimer.start(); if ( !clickTimer.running() ) clickTimer.start();
this->addHook( "window:Mouse.Click", [&](pod::payloads::windowMouseClick& payload){ this->addHook( "window:Mouse.Click", [&](pod::payloads::windowMouseClick& payload){
if ( metadata.world ) return; if ( metadata.space != "screen" ) return;
//if ( !metadata.boxMin && !metadata.boxMax ) return;
if ((metadata.boxMin.x > metadata.boxMax.x)||(metadata.boxMin.y > metadata.boxMax.y)) 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 }; pod::Vector2ui guiSize = pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height };
bool clicked = false; bool clicked = false;
@ -386,12 +354,9 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
hoverTimer.start( uf::Time<>(-1000000) ); hoverTimer.start( uf::Time<>(-1000000) );
this->addHook( "window:Mouse.Moved", [&](pod::payloads::windowMouseMoved& payload){ this->addHook( "window:Mouse.Moved", [&](pod::payloads::windowMouseMoved& payload){
if ( metadata.world ) return; if ( metadata.space != "screen" ) return;
//if ( !metadata.boxMin && !metadata.boxMax ) return;
if ((metadata.boxMin.x > metadata.boxMax.x)||(metadata.boxMin.y > metadata.boxMax.y)) 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 }; pod::Vector2ui guiSize = pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height };
bool hovered = false; bool hovered = false;
@ -488,22 +453,18 @@ void ext::GuiBehavior::tick( uf::Object& self ) {
auto& controller = scene.getController(); auto& controller = scene.getController();
auto& camera = controller.getComponent<uf::Camera>(); auto& camera = controller.getComponent<uf::Camera>();
// uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager"); if ( metadata.space == "screen" ) {
// pod::Vector2ui guiSize = manager ? manager->getComponent<ext::GuiManagerBehavior::Metadata>().size : pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height }; pod::Vector2f guiSize = pod::Vector2f{ uf::renderer::settings::width, uf::renderer::settings::height };
pod::Vector2ui guiSize = pod::Vector2ui{ uf::renderer::settings::width, uf::renderer::settings::height }; pod::Vector2f scale = { 1, 1 };
if ( metadata.scaleMode == "fixed" || metadata.scaleMode == "fixed-x" ) { if ( metadata.scaling == "fixed" || metadata.scaling == "fixed-x" ) scale.x = (float) metadata.size.x / guiSize.x;
flatten.scale.x *= (float) metadata.size.x / (float) guiSize.x; if ( metadata.scaling == "fixed" || metadata.scaling == "fixed-y" ) scale.y = (float) metadata.size.x / guiSize.y;
}
if ( metadata.scaleMode == "fixed" || metadata.scaleMode == "fixed-y" ) {
flatten.scale.y *= (float) metadata.size.x / (float) guiSize.y;
}
if ( metadata.scaleMode == "relative" || metadata.scaleMode == "relative-x" ) { if ( metadata.scaling == "relative" || metadata.scaling == "relative-x" ) scale.x = (float) guiSize.y / guiSize.x;
flatten.scale.x *= (float) guiSize.y / (float) guiSize.x; if ( metadata.scaling == "relative" || metadata.scaling == "relative-y" ) scale.y = (float) guiSize.x / guiSize.y;
}
if ( metadata.scaleMode == "relative" || metadata.scaleMode == "relative-y" ) { flatten.scale.x *= scale.x;
flatten.scale.y *= (float) guiSize.x / (float) guiSize.y; flatten.scale.y *= scale.y;
} }
// bind UBO // bind UBO
@ -619,33 +580,48 @@ void ext::GuiBehavior::tick( uf::Object& self ) {
void ext::GuiBehavior::render( uf::Object& self ){} void ext::GuiBehavior::render( uf::Object& self ){}
void ext::GuiBehavior::destroy( uf::Object& self ){} void ext::GuiBehavior::destroy( uf::Object& self ){}
void ext::GuiBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ){ 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["depth"] = /*this->*/depth;
serializer["mode"] = /*this->*/mode; serializer["mode"] = /*this->*/mode;
serializer["renderMode"] = /*this->*/renderMode;
serializer["scaling"] = /*this->*/scaleMode;
serializer["clickable"] = /*this->*/clickable; serializer["clickable"] = /*this->*/clickable;
serializer["clicked"] = /*this->*/clicked; serializer["clicked"] = /*this->*/clicked;
serializer["hoverable"] = /*this->*/hoverable; serializer["hoverable"] = /*this->*/hoverable;
serializer["hovered"] = /*this->*/hovered; 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 ){ 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->*/depth = serializer["depth"].as( /*this->*/depth );
/*this->*/mode = serializer["mode"].as( /*this->*/mode ); /*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->*/clickable = serializer["clickable"].as( /*this->*/clickable );
// /*this->*/clicked = serializer["clicked"].as( /*this->*/clicked );
/*this->*/hoverable = serializer["hoverable"].as( /*this->*/hoverable ); /*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 #undef this
@ -653,7 +629,6 @@ void ext::GuiBehavior::Metadata::deserialize( uf::Object& self, uf::Serializer&
#include <uf/ext/lua/component.h> #include <uf/ext/lua/component.h>
UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(ext::GuiBehavior::Metadata, 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::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::depth),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::mode), UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::mode),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::clickable), 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::hoverable),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::hovered), 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::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::uv),
UF_LUA_REGISTER_USERTYPE_MEMBER(ext::GuiBehavior::Metadata::color), 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::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::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( UF_BEHAVIOR_DEFINE_METADATA(
bool initialized = false; bool initialized = false;
bool world = false;
float depth = 0; float depth = 0;
size_t mode = 0; size_t mode = 0;
@ -31,15 +29,19 @@ namespace ext {
bool hovered = false; bool hovered = false;
pod::Vector2ui size = {}; pod::Vector2ui size = {};
pod::Vector2f scale = { 1, 1 };
pod::Vector4f uv = { 0, 0, 1, 1 }; pod::Vector4f uv = { 0, 0, 1, 1 };
pod::Vector4f color = { 1, 1, 1, 1 }; pod::Vector4f color = { 1, 1, 1, 1 };
uf::stl::string renderMode = "Gui"; uf::stl::string renderMode = "Gui";
uf::stl::string scaleMode = "fixed"; uf::stl::string scaling = "fixed";
pod::Vector2f boxMin = { 1, 1 }; pod::Vector2f boxMin = { 1, 1 };
pod::Vector2f boxMax = { 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; return seed;
} }
uf::stl::vector<::GlyphBox> generateGlyphs( uf::Object& self, const uf::stl::string& _string ) { // default to left
uf::stl::vector<::GlyphBox> gs; 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 UF_USE_FREETYPE if ( anchor == "top-left" ) return {0.0f, 0.0f};
auto& glyphs = ::glyphs; if ( anchor == "top-right" ) return {1.0f, 0.0f};
auto& metadata = this->getComponent<ext::GuiGlyphBehavior::Metadata>();
auto& metadataGui = this->getComponent<ext::GuiBehavior::Metadata>();
auto& transform = this->getComponent<pod::Transform<>>();
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; if ( anchor == "bottom-center" || anchor == "bottom" ) return {0.5f, 1.0f};
auto font = uf::io::root+"/fonts/" + metadata.font; if ( anchor == "bottom-left" ) return {0.0f, 1.0f};
auto color = metadataGui.color; if ( anchor == "bottom-right" ) return {1.0f, 1.0f};
auto origin = uf::stl::string("top"); return def;
auto align = uf::stl::string("center"); }
auto direction = uf::stl::string("down");
if ( glyphs.cache[font].empty() ) ext::freetype::initialize( glyphs.glyph, font ); struct TextToken {
uf::stl::string text;
pod::Vector3f color;
};
struct { float hexToFloat( const uf::stl::string& str ) {
struct { int value;
float x = 0; uf::stl::stringstream stream;
float y = 0; stream << str;
} origin; stream >> std::hex >> value;
return value / 255.0f;
struct { }
float x = 0;
float y = 0;
} cursor;
struct { // parses a text for special tokens, associating strings with color
float sum = 0; // should maybe be utf8, but in theory it shouldn't matter since tags are ASCII
float len = 0; uf::stl::vector<TextToken> parseTextTokens( const uf::stl::string& text, pod::Vector3f color ) {
float proc = 0; uf::stl::vector<TextToken> tokens;
float tab = 4;
} average;
struct {
float x = 0;
float y = 0;
} biggest;
struct { auto tagLength = 10; // hard-coded cringe
float w = 0; bool colorChanged = false;
float h = 0; size_t currentPos = 0;
} box; size_t textLength = text.length();
struct { TextToken currentToken;
uf::stl::vector<pod::Vector3f> container; currentToken.color = color;
size_t index = 0;
} colors;
} stat;
stat.colors.container.push_back(color); while ( currentPos < textLength ) {
size_t tagStart = text.find("${#", currentPos);
// grab escaped color markers: ${#RRGGBB} if ( tagStart == uf::stl::string::npos ) {
{ currentToken.text += text.substr(currentPos);
uf::stl::unordered_map<size_t, pod::Vector3f> colors; if ( !currentToken.text.empty() || colorChanged ) {
uf::stl::string text = string; tokens.emplace_back(currentToken);
}
std::regex regex("\\$\\{\\#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})\\}"); break;
std::smatch match; }
bool matched = false; if ( tagStart > currentPos ) {
currentToken.text += text.substr( currentPos, tagStart - currentPos ); // append everything up to the tag
int maxTries = 128; tokens.emplace_back( currentToken ); // commit token
while ( (matched = std::regex_search( text, match, regex )) && --maxTries > 0 ) { currentToken.text = ""; // reset text
struct { }
uf::stl::string str;
int dec; // validate tag
} r, g, b; if ( tagStart + tagLength <= textLength && text[tagStart + tagLength - 1] == '}' ) {
r.str = match[1].str(); uf::stl::string rHex = text.substr(tagStart + 3, 2);
g.str = match[2].str(); uf::stl::string gHex = text.substr(tagStart + 5, 2);
b.str = match[3].str(); uf::stl::string bHex = text.substr(tagStart + 7, 2);
{ uf::stl::stringstream stream; stream << r.str; stream >> std::hex >> r.dec; } // change color
{ uf::stl::stringstream stream; stream << g.str; stream >> std::hex >> g.dec; } currentToken.color = { hexToFloat(rHex), hexToFloat(gHex), hexToFloat(bHex) };
{ uf::stl::stringstream stream; stream << b.str; stream >> std::hex >> b.dec; } colorChanged = true;
pod::Vector3f color = { r.dec / 255.0f, g.dec / 255.0f, b.dec / 255.0f }; // advance past the tag
currentPos = tagStart + tagLength;
stat.colors.container.push_back(color); } else {
text = uf::string::replace( text, "${" + r.str + g.str + b.str + "}", "\x7F" ); 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 return tokens;
{ }
// 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];
// 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() ) { if ( !glyph.generated() ) {
glyph.setPadding( { metadata.padding[0], metadata.padding[1] } ); glyph.setPadding({ metadata.padding[0], metadata.padding[1] });
glyph.setSpread( metadata.spread ); glyph.setSpread(metadata.spread);
glyph.useSdf( metadata.sdf ); glyph.useSdf(metadata.sdf);
glyph.generate( glyphs.glyph, c, metadata.size ); glyph.generate(::glyphs.glyph, c, metadata.size);
} }
tallestGlyphY = std::max(tallestGlyphY, (float) glyph.getSize().y);
stat.biggest.x = std::max( (float) stat.biggest.x, (float) glyph.getSize().x); totalWidth += glyph.getSize().x; // should probably be reset on new-line to find the widest line
stat.biggest.y = std::max( (float) stat.biggest.y, (float) glyph.getSize().y); charCount++;
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 if ( charCount > 0 ) averageTabWidth = (totalWidth / charCount) * 4.0f;
stat.cursor.x = 0; cursor.y = tallestGlyphY;
stat.cursor.y = 0;
stat.origin.x = 0;
stat.origin.y = 0;
for ( auto it = str.begin(); it != str.end(); ++it ) { // calculate positions
uint64_t c = *it; if ( c == '\n' ) { for ( const auto& token : tokens ) {
stat.cursor.y += stat.biggest.y; std::u8string str( token.text.begin(), token.text.end() );
stat.cursor.x = 0; for ( uint64_t c : str ) {
// advance cursor on special characters
if ( c == '\n' ) {
cursor.y += tallestGlyphY;
cursor.x = 0;
continue; continue;
} else if ( c == '\t' ) { } else if ( c == '\t' ) {
// Fixed movement vs Real Tabbing cursor.x = ((int)(cursor.x / averageTabWidth) + 1) * averageTabWidth;
if ( false ) {
stat.cursor.x += stat.average.tab;
} else {
stat.cursor.x = ((stat.cursor.x / stat.average.tab) + 1) * stat.average.tab;
}
continue; continue;
} else if ( c == COLOR_CTRL ) { } else if ( c == ' ' ) {
cursor.x += averageTabWidth / 4.0f;
continue; continue;
} }
auto key = hashGlyphSettings( c, metadata );
auto& glyph = glyphs.cache[font][key];
::GlyphBox g;
g.box.w = glyph.getSize().x; // retrieve glyph
g.box.h = glyph.getSize().y; auto key = hashGlyphSettings(c, metadata);
auto& glyph = glyphsCache[key];
g.box.x = stat.cursor.x + glyph.getBearing().x; auto& g = layout.emplace_back(GlyphBox{
g.box.y = stat.cursor.y - glyph.getBearing().y; // - (glyph.getSize().y - glyph.getBearing().y); .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 // calculate offset based on anchor
stat.cursor.x = 0; float offsetX = maxTextWidth * anchor.x;
stat.cursor.y = stat.biggest.y; float offsetY = maxTextHeight * anchor.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; // adjust all glyphs for our offset
g.code = c; for ( auto& g : layout ) {
g.box.x -= offsetX;
g.box.w = glyph.getSize().x; g.box.y -= offsetY;
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;
}
// normalize
g.box.x /= ::defaults.size.x; g.box.x /= ::defaults.size.x;
g.box.w /= ::defaults.size.x; g.box.w /= ::defaults.size.x;
g.box.y /= ::defaults.size.y; g.box.y /= ::defaults.size.y;
g.box.h /= ::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 #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& metadataGui = this->getComponent<ext::GuiBehavior::Metadata>();
auto& metadataJson = this->getComponent<uf::Serializer>(); 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){ this->addHook( "gui:UpdateText.%UID%", [&](ext::json::Value& payload){
auto string = payload["string"].as(metadata.string); auto string = payload["string"].as(metadata.string);
auto font = uf::io::root+"/fonts/" + payload["font"].as(metadata.font); auto font = uf::io::root+"/fonts/" + payload["font"].as(metadata.font);
bool forced = payload["force"].as(false); bool forced = payload["force"].as(false);
metadata.sdf = false; // override
// metadata.sdf = false;
auto glyphs = ::generateGlyphs( self, string ); metadataGui.scaling = "none";
auto& scene = uf::scene::getCurrentScene(); auto& scene = uf::scene::getCurrentScene();
auto& mesh = this->getComponent<uf::Mesh>(); 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; uf::stl::unordered_map<size_t, uf::stl::string> glyph_atlas_map;
metadataGui.scaleMode = "none";
auto tokens = ::parseTextTokens(string, metadataGui.color);
// generate texture auto layout = ::calculateGlyphLayout(self, tokens, metadata, metadataGui, font);
#if UF_USE_FREETYPE ::generateAtlasAndMesh( layout, metadata, font, atlas, mesh );
{
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 );
}
// set proper shaders
if ( metadata.sdf ) { if ( metadata.sdf ) {
metadataJson["shaders"]["vertex"] = uf::io::root+"/shaders/gui/text/vert.spv"; metadataJson["shaders"]["vertex"] = uf::io::root+"/shaders/gui/text/vert.spv";
metadataJson["shaders"]["fragment"] = uf::io::root+"/shaders/gui/text/frag.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)*/ int32_t spread;
/*alignas(4)*/ float weight; /*alignas(4)*/ float weight;
/*alignas(4)*/ float fillWeight;
/*alignas(4)*/ float scale; /*alignas(4)*/ float scale;
/*alignas(4)*/ float padding1; /*alignas(4)*/ float padding1;
/*alignas(4)*/ float padding2; /*alignas(4)*/ float padding2;
/*alignas(4)*/ float padding3;
} ubo = { } ubo = {
.stroke = metadata.shader.stroke, .stroke = metadata.shader.stroke,
.range = metadata.shader.range, .range = metadata.shader.range,
.spread = metadata.spread, .spread = metadata.spread,
.weight = metadata.shader.weight, .weight = metadata.shader.weight,
.fillWeight = metadata.shader.fillWeight,
.scale = metadata.shader.scale, .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 ); 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["scale"] = /*this->*/shader.scale;
serializer["weight"] = /*this->*/shader.weight; serializer["weight"] = /*this->*/shader.weight;
serializer["fillWeight"] = /*this->*/shader.fillWeight;
serializer["stroke"] = uf::vector::encode( /*this->*/shader.stroke); serializer["stroke"] = uf::vector::encode( /*this->*/shader.stroke);
serializer["range"] = uf::vector::encode( /*this->*/shader.range); 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.scale = serializer["scale"].as(/*this->*/shader.scale);
/*this->*/shader.weight = serializer["weight"].as(/*this->*/shader.weight); /*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.stroke = uf::vector::decode(serializer["stroke"], /*this->*/shader.stroke);
/*this->*/shader.range = uf::vector::decode(serializer["range"], /*this->*/shader.range); /*this->*/shader.range = uf::vector::decode(serializer["range"], /*this->*/shader.range);

View File

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

View File

@ -535,6 +535,7 @@ namespace {
} }
void bindInstanceAddresses( uf::renderer::Graphic& graphic, uf::Mesh& mesh, uf::stl::vector<pod::Instance::Addresses>& addresses ) { 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; if ( !uf::renderer::settings::invariant::deviceAddressing || !mesh.indirect.count ) return;
addresses.resize( mesh.indirect.count ); addresses.resize( mesh.indirect.count );
@ -574,6 +575,7 @@ namespace {
instanceAddresses.drawID = drawID; instanceAddresses.drawID = drawID;
} }
} }
#endif
} }
} }
@ -1576,6 +1578,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) {
if ( !soft ) { if ( !soft ) {
storage.buffers.camera.destroy(true); storage.buffers.camera.destroy(true);
storage.buffers.drawCommands.destroy(true); storage.buffers.drawCommands.destroy(true);
storage.buffers.lodMetadata.destroy(true);
storage.buffers.instance.destroy(true); storage.buffers.instance.destroy(true);
storage.buffers.instanceAddresses.destroy(true); storage.buffers.instanceAddresses.destroy(true);
storage.buffers.joint.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.material.destroy(true);
storage.buffers.texture.destroy(true); storage.buffers.texture.destroy(true);
storage.buffers.light.destroy(true); storage.buffers.light.destroy(true);
storage.buffers.depthPyramid.destroy(true);
} }
uf::renderer::states::rebuild = true; uf::renderer::states::rebuild = true;
@ -1777,6 +1781,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
drawCommands[drawID].vertexID = 0; drawCommands[drawID].vertexID = 0;
} else { } else {
drawCommands[drawID] = primitives[drawID].drawCommand; 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" ) { if ( attribute.name == "POSITION" ) {
meshlet.vertices.resize(accessor.count); 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.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] }; 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 ) { switch ( accessor.componentType ) {

View File

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