#include "behavior.h" #include "./glyph/behavior.h" #include "./manager/behavior.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "./payload.h" #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; #if EXT_COLOR_FLOATS pod::Vector4f color; #else pod::ColorRgba color; #endif static uf::stl::vector descriptor; }; template struct UniformDescriptor { struct Matrices { /*alignas(16)*/ pod::Matrix4f model; } matrices[N]; struct Gui { /*alignas(16)*/ pod::Vector4f offset; /*alignas(16)*/ pod::Vector4f color; /*alignas(4)*/ int32_t mode = 0; /*alignas(4)*/ float depth = 0.0f; /*alignas(8)*/ float padding1; /*alignas(8)*/ float padding2; } gui; }; } UF_VERTEX_DESCRIPTOR(Mesh, UF_VERTEX_DESCRIPTION(Mesh, R32G32B32_SFLOAT, position) UF_VERTEX_DESCRIPTION(Mesh, R32G32_SFLOAT, uv) #if EXT_COLOR_FLOATS UF_VERTEX_DESCRIPTION(Mesh, R32G32B32A32_SFLOAT, color) #else UF_VERTEX_DESCRIPTION(Mesh, R8G8B8A8_UNORM, color) #endif ) namespace { uf::Image& generateImage( uf::Image& image ) { image.open(uf::io::root+"/textures/missing.png"); return image; } uf::Mesh& generateMesh( uf::Mesh& mesh, const pod::Vector4f& color = {1, 1, 1, 1} ) { mesh.bind<::Mesh, uint16_t>(); mesh.insertVertices<::Mesh>({ { 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{0.0f, 1.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}, pod::Vector2f{0.0f, 0.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{1.0f, 1.0f}, color }, }); mesh.insertIndices({ 0, 1, 2, 3, 4, 5 }); return mesh; } } // YUCK UF_OBJECT_REGISTER_BEGIN(ext::Gui) // UF_OBJECT_REGISTER_BEHAVIOR(uf::EntityBehavior) UF_OBJECT_REGISTER_BEHAVIOR(uf::ObjectBehavior) UF_OBJECT_REGISTER_BEHAVIOR(ext::GuiBehavior) UF_OBJECT_REGISTER_BEHAVIOR(ext::GuiGlyphBehavior) UF_OBJECT_REGISTER_END() UF_BEHAVIOR_REGISTER_CPP(ext::GuiBehavior) UF_BEHAVIOR_TRAITS_CPP(ext::GuiBehavior, ticks = true, renders = false, multithread = false) #define this (&self) void ext::GuiBehavior::initialize( uf::Object& self ) { auto& metadata = this->getComponent(); auto& metadataJson = this->getComponent(); auto& transform = this->getComponent>(); auto& mesh = this->getComponent(); 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() ) { 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(); /* if ( metadataJson["mode"].as() == "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() ) { // 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() ) for ( auto& v : vertices ) v.uv.y = 1 - v.uv.y; #endif // for ( auto& v : vertices ) v.position.z = metadata.depth; } */ graphic.descriptor.parse( metadataJson ); // bind texture data if ( payload.image ) { if ( metadata.initialized ) { for ( auto& texture : graphic.material.textures ) texture.destroy(); graphic.material.textures.clear(); } auto& image = *payload.image; metadata.size = image.getDimensions(); auto& texture = graphic.material.textures.emplace_back(); texture.loadFromImage( image ); // update transform { } if ( payload.free ) { delete payload.image; payload.image = {}; } } // bind mesh data if ( payload.mesh ) { auto& mesh = *payload.mesh; uf::Mesh::Attribute positionAttribute; uf::Mesh::Attribute uvAttribute; for ( auto& attribute : mesh.vertex.attributes ) { if ( attribute.descriptor.name == "position" ) positionAttribute = attribute; if ( attribute.descriptor.name == "uv" ) uvAttribute = attribute; } #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() ) for ( auto& v : vertices ) v.uv.y = 1 - v.uv.y; #endif #if UF_USE_OPENGL for ( auto i = 0; i < mesh.vertex.count; ++i ) { // set depth float* position = (float*) (static_cast(positionAttribute.pointer) + i * positionAttribute.stride ); position[2] = metadata.depth; // flip /* float* uv = (float*) (static_cast(uvAttribute.pointer) + i * uvAttribute.stride ); uv[1] = 1.0f - uv[1]; */ } #endif if ( !metadata.initialized ) { graphic.initialize( metadata.renderMode ); graphic.initializeMesh( mesh ); struct { uf::stl::string vertex = uf::io::root+"/shaders/gui/base/vert.spv"; uf::stl::string fragment = uf::io::root+"/shaders/gui/base/frag.spv"; } filenames; uf::stl::string suffix = ""; { uf::stl::string _ = scene.getComponent()["shaders"]["gui"]["suffix"].as(); if ( _ != "" ) suffix = _ + "."; } if ( metadataJson["shaders"]["vertex"].is() ) filenames.vertex = metadataJson["shaders"]["vertex"].as(); if ( metadataJson["shaders"]["fragment"].is() ) filenames.fragment = metadataJson["shaders"]["fragment"].as(); else if ( suffix != "" ) filenames.fragment = uf::io::root+"/shaders/gui/"+suffix+"base/frag.spv"; graphic.material.initializeShaders({ {filenames.vertex, uf::renderer::enums::Shader::VERTEX}, {filenames.fragment, uf::renderer::enums::Shader::FRAGMENT}, }); #if UF_USE_VULKAN { auto& shader = graphic.material.getShader("vertex"); struct SpecializationConstant { uint32_t passes = 6; }; auto& specializationConstants = shader.specializationConstants.get(); specializationConstants.passes = uf::renderer::settings::maxViews; } #endif } else { graphic.initializeMesh( mesh ); graphic.getPipeline().update( graphic ); } if ( payload.free ) { delete payload.mesh; payload.mesh = {}; } } metadata.initialized = true; }); this->addHook( "asset:Load.%UID%", [&](pod::payloads::assetLoad& payload){ if ( !uf::asset::isExpected( payload, uf::asset::Type::IMAGE ) ) return; if ( !uf::asset::has( payload ) ) uf::asset::load( payload ); auto& image = uf::asset::get( payload ); // generate default mesh ::generateMesh( mesh, metadata.color ); if ( ext::json::isNull(metadataJson["cull mode"]) ) metadataJson["cull mode"] = "front"; { ext::payloads::GuiInitializationPayload payload; payload.image = ℑ payload.mesh = &mesh; this->callHook("gui:Update.%UID%", payload); } }); if ( metadata.ui.click.able ) { uf::Timer clickTimer(false); clickTimer.start( uf::Time<>(-1000000) ); if ( !clickTimer.running() ) clickTimer.start(); this->addHook( "window:Mouse.Click", [&](pod::payloads::windowMouseClick& payload){ if ( metadata.world ) return; //if ( !metadata.box.min && !metadata.box.max ) return; if ((metadata.box.min.x > metadata.box.max.x)||(metadata.box.min.y > metadata.box.max.y)) return; // uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager"); // pod::Vector2ui guiSize = manager ? manager->getComponent().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; if ( payload.mouse.state == -1 ) { pod::Vector2f click; click.x = (float) payload.mouse.position.x / (float) guiSize.x; click.y = (float) payload.mouse.position.y / (float) guiSize.y; click.x = (click.x * 2.0f) - 1.0f; click.y = (click.y * 2.0f) - 1.0f; #if UF_USE_OPENGL click.y = -click.y; #endif float x = click.x; float y = click.y; if (payload.invoker == "vr" ) { x = payload.mouse.position.x; y = payload.mouse.position.y; } clicked = ( metadata.box.min.x <= x && metadata.box.min.y <= y && metadata.box.max.x >= x && metadata.box.max.y >= y ); int minX = (metadata.box.min.x * 0.5f + 0.5f) * guiSize.x; int minY = (metadata.box.min.y * 0.5f + 0.5f) * guiSize.y; int maxX = (metadata.box.max.x * 0.5f + 0.5f) * guiSize.x; int maxY = (metadata.box.max.y * 0.5f + 0.5f) * guiSize.y; int mouseX = payload.mouse.position.x; int mouseY = payload.mouse.position.y; } metadata.ui.click.ed = clicked; if ( clicked ) { this->callHook("gui:Clicked.%UID%", payload); } this->callHook("gui:Mouse.Clicked.%UID%", payload); } ); this->addHook( "gui:Clicked.%UID%", [&]( ext::json::Value& json ){ pod::payloads::windowMouseClick payload; this->callHook("gui:Clicked.%UID%", payload); }); this->addHook( "gui:Clicked.%UID%", [&](pod::payloads::windowMouseClick& payload){ if ( ext::json::isObject( metadataJson["events"]["click"] ) ) { ext::json::Value event = metadataJson["events"]["click"]; metadataJson["events"]["click"] = ext::json::array(); metadataJson["events"]["click"][0] = event; } else if ( !ext::json::isArray( metadataJson["events"]["click"] ) ) { this->getParent().as().callHook("gui:Clicked.%UID%", payload); return; } for ( int i = 0; i < metadataJson["events"]["click"].size(); ++i ) { ext::json::Value event = metadataJson["events"]["click"][i]; ext::json::Value payload = event["payload"]; if ( event["delay"].is() ) { this->queueHook(event["name"].as(), payload, event["delay"].as()); } else { this->callHook(event["name"].as(), payload ); } } }); } if ( metadata.ui.hover.able ) { uf::Timer hoverTimer(false); hoverTimer.start( uf::Time<>(-1000000) ); this->addHook( "window:Mouse.Moved", [&](pod::payloads::windowMouseMoved& payload){ if ( metadata.world ) return; //if ( !metadata.box.min && !metadata.box.max ) return; if ((metadata.box.min.x > metadata.box.max.x)||(metadata.box.min.y > metadata.box.max.y)) return; // uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager"); // pod::Vector2ui guiSize = manager ? manager->getComponent().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; pod::Vector2f click; click.x = (float) payload.mouse.position.x / (float) guiSize.x; click.y = (float) payload.mouse.position.y / (float) guiSize.y; click.x = (click.x * 2.0f) - 1.0f; click.y = (click.y * 2.0f) - 1.0f; #if UF_USE_OPENGL click.y = -click.y; #endif float x = click.x; float y = click.y; hovered = ( metadata.box.min.x <= x && metadata.box.min.y <= y && metadata.box.max.x >= x && metadata.box.max.y >= y ); if ( hovered ) { int minX = (metadata.box.min.x * 0.5f + 0.5f) * guiSize.x; int minY = (metadata.box.min.y * 0.5f + 0.5f) * guiSize.y; int maxX = (metadata.box.max.x * 0.5f + 0.5f) * guiSize.x; int maxY = (metadata.box.max.y * 0.5f + 0.5f) * guiSize.y; int mouseX = payload.mouse.position.x; int mouseY = payload.mouse.position.y; } metadata.ui.hover.ed = hovered; if ( hovered && hoverTimer.elapsed().asDouble() >= 1 ) { hoverTimer.reset(); this->callHook("gui:Hovered.%UID%", payload); } this->callHook("gui:Mouse.Moved.%UID%", payload); }); this->addHook( "gui:Hovered.%UID%", [&](ext::json::Value& json){ if ( ext::json::isObject( metadataJson["events"]["hover"] ) ) { ext::json::Value event = metadataJson["events"]["hover"]; metadataJson["events"]["hover"] = ext::json::array(); //Json::arrayValue; metadataJson["events"]["hover"][0] = event; } else if ( !ext::json::isArray( metadataJson["events"]["hover"] ) ) { this->getParent().as().callHook("gui:Clicked.%UID%", json); return; } for ( int i = 0; i < metadataJson["events"]["hover"].size(); ++i ) { ext::json::Value event = metadataJson["events"]["hover"][i]; ext::json::Value payload = event["payload"]; float delay = event["delay"].as(); if ( event["delay"].is() ) { this->queueHook(event["name"].as(), payload, event["delay"].as()); } else { this->callHook(event["name"].as(), payload ); } } return; }); } } void ext::GuiBehavior::tick( uf::Object& self ) { if ( !this->hasComponent() ) return; auto& transform = this->getComponent>(); auto& metadata = this->getComponent(); auto& metadataJson = this->getComponent(); auto& mesh = this->getComponent(); auto& graphic = this->getComponent(); auto model = uf::matrix::identity(); auto flatten = uf::transform::flatten( transform ); auto& scene = uf::scene::getCurrentScene(); auto& controller = scene.getController(); auto& camera = controller.getComponent(); // uf::Object* manager = (uf::Object*) this->globalFindByName("Gui Manager"); // pod::Vector2ui guiSize = manager ? manager->getComponent().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.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.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; } // bind UBO #if UF_USE_OPENGL { if ( metadata.mode == 0 ) { transform.position.y = -transform.position.y; } model = uf::transform::model( transform ); auto& shader = graphic.material.getShader("vertex"); pod::Uniform uniform; if ( metadata.mode == 1 ) { uniform.modelView = model; uniform.projection = uf::matrix::identity(); } else if ( metadata.mode == 2 ) { uniform.modelView = camera.getView() * uf::transform::model( transform ); uniform.projection = camera.getProjection(); } else if ( metadata.mode == 3 ) { uniform.modelView = uf::matrix::translate( uf::matrix::identity(), flatten.position ) * uf::matrix::scale( uf::matrix::identity(), flatten.scale ) * uf::quaternion::matrix( flatten.orientation ) * flatten.model; uniform.projection = camera.getProjection(); } else { uniform.modelView = uf::matrix::translate( uf::matrix::identity(), flatten.position ) * uf::matrix::scale( uf::matrix::identity(), flatten.scale ) * uf::quaternion::matrix( flatten.orientation ) * flatten.model; uniform.projection = uf::matrix::identity(); } shader.updateUniform( "UBO", (const void*) &uniform, sizeof(uniform) ); } #else if ( graphic.material.hasShader("vertex") ) { auto& shader = graphic.material.getShader("vertex"); if ( shader.hasUniform("UBO") ) { auto& uniformBuffer = shader.getUniformBuffer("UBO"); UniformDescriptor<> uniforms{}; for ( auto i = 0; i < uf::renderer::settings::maxViews; ++i ) { if ( metadata.mode == 1 ) { uniforms.matrices[i].model = transform.model; } else if ( metadata.mode == 2 ) { auto& scene = uf::scene::getCurrentScene(); auto& controller = scene.getController(); auto& camera = controller.getComponent(); uniforms.matrices[i].model = camera.getProjection(i) * camera.getView(i) * uf::transform::model( transform ); } else if ( metadata.mode == 3 ) { uniforms.matrices[i].model = camera.getProjection(i) * uf::matrix::translate( uf::matrix::identity(), flatten.position ) * uf::matrix::scale( uf::matrix::identity(), flatten.scale ) * uf::quaternion::matrix( flatten.orientation ) * flatten.model; } else { uniforms.matrices[i].model = uf::matrix::translate( uf::matrix::identity(), flatten.position ) * uf::matrix::scale( uf::matrix::identity(), flatten.scale ) * uf::quaternion::matrix( flatten.orientation ) * flatten.model; } } uniforms.gui = ::UniformDescriptor<>::Gui{ .offset = metadata.uv, .color = metadata.color, .mode = 0, // metadata.shader, .depth = uf::matrix::reverseInfiniteProjection ? 1 - metadata.depth : metadata.depth, }; shader.updateBuffer( (const void*) &uniforms, sizeof(uniforms), uniformBuffer ); model = uniforms.matrices[0].model; } } #endif // calculate bounding box { pod::Vector2f min = { std::numeric_limits::max(), std::numeric_limits::max() }; pod::Vector2f max = { -std::numeric_limits::max(), -std::numeric_limits::max() }; uf::Mesh::Attribute vertexAttribute; for ( auto& attribute : mesh.vertex.attributes ) if ( attribute.descriptor.name == "position" ) { vertexAttribute = attribute; break; } UF_ASSERT( vertexAttribute.descriptor.name == "position" ); for ( auto i = 0; i < mesh.vertex.count; ++i ) { float* p = (float*) (static_cast(vertexAttribute.pointer) + i * vertexAttribute.stride ); // pod::Vector4f position = { p[0], p[1], p[2], 1 }; pod::Vector4f position = { p[0], p[1], 0, 1 }; pod::Vector4f translated = uf::matrix::multiply( model, position ); min.x = std::min( min.x, translated.x ); max.x = std::max( max.x, translated.x ); min.y = std::min( min.y, translated.y ); max.y = std::max( max.y, translated.y ); } metadata.box.min.x = min.x; metadata.box.min.y = min.y; metadata.box.max.x = max.x; metadata.box.max.y = max.y; } } 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->*/ui.click.able; serializer["clicked"] = /*this->*/ui.click.ed; serializer["hoverable"] = /*this->*/ui.hover.able; serializer["hovered"] = /*this->*/ui.hover.ed; } 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->*/ui.click.able = serializer["clickable"].as( /*this->*/ui.click.able ); /*this->*/ui.hover.able = serializer["hoverable"].as( /*this->*/ui.hover.able ); } #undef this